{
  "openapi": "3.1.0",
  "info": {
    "title": "JagaDana API",
    "version": "0.2.0",
    "summary": "Kontrak API lapisan transparansi pengadaan",
    "description": "API JagaDana — lapisan transparansi pengadaan. Semua data paket, penyedia, dan penawaran adalah **data demo sintetis** berlabel “Data Demo”; tidak ada data nyata atau hasil scraping.\n\n**Aturan lintas endpoint**\n- Nominal uang: rupiah bulat (integer), tanpa pecahan. Contoh `190342598`.\n- Waktu: ISO-8601 dengan zona, mis. `2026-11-15T08:30:00+07:00`.\n- ID: UUID v4.\n- Daftar memakai cursor: `?limit=&cursor=` → `{ items, next_cursor }`.\n- Galat selalu memakai satu envelope: `{ error: { code, message, fields? }, request_id? }`.\n- Angka audit (deviasi, sinkronisasi, kemiripan, skor risiko) dihitung kode deterministik, bukan LLM.\n\n**Penawaran tersegel**: selama masa penawaran, publik hanya melihat hash komitmen. Harga dan identitas penawar dibuka setelah `bids/open`, dan rincian pemenang dibuka setelah penetapan + masa sanggah.\n\n**Penanda status**: `x-status: implemented` berarti endpoint sudah ada di kode; `planned` berarti kontrak sudah disepakati dan implementasinya menyusul pada fase berikutnya. Dokumentasi ini adalah kontrak, bukan klaim bahwa semua endpoint sudah berjalan.\n\n**Bahasa temuan netral**: sistem menandai “Perlu Klarifikasi” sebagai indikator untuk dijelaskan, bukan bukti pelanggaran.",
    "contact": {
      "name": "Tim JagaDana"
    },
    "license": {
      "name": "Prototipe kompetisi (bukan untuk produksi)",
      "identifier": "LicenseRef-Proprietary"
    }
  },
  "servers": [
    {
      "url": "/",
      "description": "Origin yang sama dengan aplikasi"
    },
    {
      "url": "http://localhost:3000",
      "description": "Pengembangan lokal"
    }
  ],
  "tags": [
    {
      "name": "Publik",
      "description": "Tanpa autentikasi. Hanya data yang boleh dilihat siapa pun."
    },
    {
      "name": "Autentikasi",
      "description": "Masuk/keluar dan sesi (Auth.js, cookie HttpOnly)."
    },
    {
      "name": "PPK — Paket & HPS",
      "description": "Membuat paket, menyusun HPS, dan mengajukannya ke Pokja."
    },
    {
      "name": "Pokja — Evaluasi & Penetapan",
      "description": "Pengumuman, pembukaan penawaran tersegel, evaluasi, penetapan, sanggah."
    },
    {
      "name": "Penyedia — Penawaran",
      "description": "Draf, pengiriman tersegel ber-hash, impor PDF, sanggah."
    },
    {
      "name": "Kontrak & Termin",
      "description": "Kontrak, jadwal termin, pengajuan pencairan, penahanan."
    },
    {
      "name": "Auditor",
      "description": "Antrean risiko, investigasi, penahanan termin, verifikasi item."
    },
    {
      "name": "Laporan Warga",
      "description": "Laporan lapangan tanpa akun, dengan klaster radius 50 m dan perlindungan privasi."
    },
    {
      "name": "Sistem",
      "description": "Kesehatan layanan, log audit, endpoint ADMIN/CLI."
    },
    {
      "name": "Referensi",
      "description": "Katalog harga acuan HSPK."
    }
  ],
  "security": [],
  "components": {
    "securitySchemes": {
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "authjs.session-token",
        "description": "Cookie sesi Auth.js (`__Secure-authjs.session-token` di HTTPS). HttpOnly, SameSite=Lax."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Token operator untuk ADMIN/CLI (`OPERATOR_TOKEN`, minimal 24 karakter). Dipertahankan hanya untuk endpoint `/api/rab/upload`."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "VALIDATION_ERROR",
                  "UNAUTHORIZED",
                  "FORBIDDEN",
                  "NOT_FOUND",
                  "CONFLICT",
                  "STATE_CONFLICT",
                  "RATE_LIMITED",
                  "PAYLOAD_TOO_LARGE",
                  "UNPROCESSABLE",
                  "UPSTREAM_ERROR",
                  "INTERNAL"
                ]
              },
              "message": {
                "type": "string",
                "description": "Ringkasan yang aman ditampilkan ke pengguna"
              },
              "fields": {
                "description": "Peta nama field → daftar pesan, hanya untuk VALIDATION_ERROR",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": false
          },
          "request_id": {
            "description": "ID permintaan untuk korelasi log",
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "description": "Envelope error tunggal untuk seluruh API. Pesan tidak memuat stack trace."
      }
    }
  },
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "system.health",
        "summary": "Status layanan",
        "description": "Dipakai healthcheck Docker/Caddy. Tidak memerlukan autentikasi dan tidak membocorkan detail konfigurasi.",
        "tags": [
          "Sistem"
        ],
        "security": [],
        "x-status": "implemented",
        "x-roles": [
          "PUBLIK"
        ],
        "responses": {
          "200": {
            "description": "Layanan dan basis data sehat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "db": {
                      "type": "string",
                      "enum": [
                        "up",
                        "down"
                      ]
                    },
                    "version": {
                      "type": "string"
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    }
                  },
                  "required": [
                    "status",
                    "db",
                    "version",
                    "time"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "503": {
            "description": "Layanan belum siap",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/rab/upload": {
      "post": {
        "operationId": "rab.upload",
        "summary": "Unggah RAB untuk satu paket (ADMIN/CLI)",
        "description": "Endpoint lama yang dipertahankan untuk ADMIN dan skrip CLI selama UI operator baru belum ada. Akan digantikan oleh alur HPS/penawaran terstruktur.\n\nAutentikasi: `Authorization: Bearer <OPERATOR_TOKEN>` (minimal 24 karakter, nilai placeholder ditolak). Perbandingan memakai `timingSafeEqual`.\n\nAlur: validasi PDF (MIME, ≤10 MB, magic bytes `%PDF-`) → dedup SHA-256 → ekstraksi Gemini → audit deterministik → simpan versi baru dalam satu transaksi (dokumen, skor risiko, status, audit log).\n\nVersi lama tidak pernah ditimpa; unggahan identik ditolak `409`.\n\nRate limit: 5 unggahan/menit per klien.",
        "tags": [
          "Sistem"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "implemented",
        "x-roles": [
          "ADMIN"
        ],
        "x-rate-limit": "5 unggahan/menit per klien",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "procurementId": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "procurementId",
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dokumen tersimpan beserta hasil audit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "documentId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "version": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "sha256": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$",
                      "description": "SHA-256 heksadesimal 64 karakter"
                    },
                    "audit": {
                      "type": "object",
                      "properties": {
                        "total_extracted_value": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                        },
                        "official_contract_value": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                        },
                        "value_difference": {
                          "type": "number"
                        },
                        "is_synced": {
                          "type": "boolean"
                        },
                        "risk_score": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        },
                        "risk_level": {
                          "type": "string",
                          "enum": [
                            "LOW",
                            "MEDIUM",
                            "HIGH"
                          ]
                        },
                        "flagged_count": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        }
                      },
                      "required": [
                        "total_extracted_value",
                        "official_contract_value",
                        "value_difference",
                        "is_synced",
                        "risk_score",
                        "risk_level",
                        "flagged_count"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "documentId",
                    "version",
                    "sha256",
                    "audit"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Berkas melebihi batas (`PAYLOAD_TOO_LARGE`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Layanan ekstraksi gagal (`UPSTREAM_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/session": {
      "get": {
        "operationId": "auth.session",
        "summary": "Sesi pengguna saat ini",
        "description": "Mengembalikan sesi aktif beserta peran, atau `session: null` bila belum masuk.\n\nSesi disimpan di cookie `HttpOnly`, `SameSite=Lax`, `Secure` di produksi (Auth.js). Peran dibaca dari tabel `users`, bukan dari klaim yang bisa diubah klien.\n\nPeran yang dikenal: `PUBLIK`, `PENYEDIA`, `POKJA`, `PPK`, `AUDITOR`, `ADMIN`.",
        "tags": [
          "Autentikasi"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "responses": {
          "200": {
            "description": "Sesi aktif atau null",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "session": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "user": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid",
                                  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                                },
                                "name": {
                                  "type": "string"
                                },
                                "email": {
                                  "type": "string",
                                  "format": "email",
                                  "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                                },
                                "role": {
                                  "type": "string",
                                  "enum": [
                                    "PUBLIK",
                                    "PENYEDIA",
                                    "POKJA",
                                    "PPK",
                                    "AUDITOR",
                                    "ADMIN"
                                  ]
                                },
                                "provider_id": {
                                  "anyOf": [
                                    {
                                      "type": "string",
                                      "format": "uuid",
                                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ],
                                  "description": "Terisi bila peran PENYEDIA"
                                }
                              },
                              "required": [
                                "id",
                                "name",
                                "role",
                                "provider_id"
                              ],
                              "additionalProperties": false
                            },
                            "expires": {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            }
                          },
                          "required": [
                            "user",
                            "expires"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "null bila belum masuk"
                    }
                  },
                  "required": [
                    "session"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/csrf": {
      "get": {
        "operationId": "auth.csrf",
        "summary": "Token CSRF untuk formulir masuk",
        "description": "Disediakan Auth.js. Ambil token, lalu kirim sebagai field `csrfToken` pada permintaan masuk.",
        "tags": [
          "Autentikasi"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "responses": {
          "200": {
            "description": "Token CSRF",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "csrfToken": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "csrfToken"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/callback/credentials": {
      "post": {
        "operationId": "auth.credentials",
        "summary": "Masuk dengan email dan kata sandi",
        "description": "Endpoint Auth.js (Credentials provider). Body `application/x-www-form-urlencoded`.\n\nKata sandi disimpan sebagai hash `scrypt` (Node `crypto`), tidak pernah dikembalikan API. Percobaan gagal dibatasi 10 per 15 menit per IP+email.\n\nAkun demo di-seed oleh `npm run db:seed`; kredensial demo hanya ditampilkan di halaman masuk saat `DEMO_MODE=true`.",
        "tags": [
          "Autentikasi"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "x-rate-limit": "10 percobaan/15 menit per IP+email",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 200
                  },
                  "csrfToken": {
                    "type": "string"
                  },
                  "callbackUrl": {
                    "type": "string"
                  }
                },
                "required": [
                  "email",
                  "password",
                  "csrfToken"
                ]
              }
            }
          }
        },
        "responses": {
          "302": {
            "description": "Redirect ke `callbackUrl` dengan cookie sesi"
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/signout": {
      "post": {
        "operationId": "auth.signout",
        "summary": "Keluar dan hapus sesi",
        "description": "Disediakan Auth.js. Menghapus cookie sesi dan mencatat aksi `LOGOUT` di audit log.",
        "tags": [
          "Autentikasi"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "csrfToken": {
                    "type": "string"
                  },
                  "callbackUrl": {
                    "type": "string"
                  }
                },
                "required": [
                  "csrfToken"
                ]
              }
            }
          }
        },
        "responses": {
          "302": {
            "description": "Redirect setelah sesi dihapus"
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/demo": {
      "post": {
        "operationId": "auth.demo",
        "summary": "Masuk cepat sebagai akun demo (hanya DEMO_MODE)",
        "description": "Dipakai tombol “Masuk sebagai PPK / Pokja / Penyedia / Auditor” pada demo juri.\n\nDitolak dengan `403` bila `DEMO_MODE` bukan `true`, sehingga endpoint ini mati di produksi biasa. Setiap pemanggilan tercatat di audit log dengan `actor_role` akun demo yang dipilih.",
        "tags": [
          "Autentikasi"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "x-rate-limit": "20/menit per IP",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "PPK",
                      "POKJA",
                      "PENYEDIA",
                      "AUDITOR",
                      "ADMIN"
                    ],
                    "description": "Peran akun demo yang ingin dipakai"
                  },
                  "provider_id": {
                    "description": "Wajib bila role=PENYEDIA dan ada lebih dari satu penyedia demo",
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  }
                },
                "required": [
                  "role"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sesi demo aktif",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "session": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "user": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid",
                                  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                                },
                                "name": {
                                  "type": "string"
                                },
                                "email": {
                                  "type": "string",
                                  "format": "email",
                                  "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
                                },
                                "role": {
                                  "type": "string",
                                  "enum": [
                                    "PUBLIK",
                                    "PENYEDIA",
                                    "POKJA",
                                    "PPK",
                                    "AUDITOR",
                                    "ADMIN"
                                  ]
                                },
                                "provider_id": {
                                  "anyOf": [
                                    {
                                      "type": "string",
                                      "format": "uuid",
                                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ],
                                  "description": "Terisi bila peran PENYEDIA"
                                }
                              },
                              "required": [
                                "id",
                                "name",
                                "role",
                                "provider_id"
                              ],
                              "additionalProperties": false
                            },
                            "expires": {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            }
                          },
                          "required": [
                            "user",
                            "expires"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "null bila belum masuk"
                    }
                  },
                  "required": [
                    "session"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages": {
      "get": {
        "operationId": "packages.list",
        "summary": "Daftar paket pengadaan",
        "description": "Daftar paket untuk peta dan tabel publik. Tanpa autentikasi.\n\nHanya menampilkan data yang boleh publik: judul, dinas, nilai pagu/HPS, status tahapan, skor risiko, dan jumlah penawaran. Harga penawaran **tidak** ikut sebelum penetapan pemenang.\n\nUrutan default: `risk_score` menurun, lalu judul.",
        "tags": [
          "Publik"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "DRAFT",
                "DIUMUMKAN",
                "PENAWARAN",
                "EVALUASI",
                "PENETAPAN",
                "SANGGAH",
                "KONTRAK",
                "PELAKSANAAN",
                "SELESAI",
                "DIBATALKAN"
              ]
            }
          },
          {
            "name": "project_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "JALAN",
                "DRAINASE",
                "GEDUNG"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "description": "Cari pada judul, dinas, atau nama penyedia",
              "type": "string"
            }
          },
          {
            "name": "min_risk",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 20,
              "description": "Jumlah item per halaman",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "description": "Cursor dari respons sebelumnya",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Halaman daftar paket",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "code": {
                            "type": "string",
                            "description": "Kode paket/RUP demo, mis. DEMO-RUP-DRN-01"
                          },
                          "title": {
                            "type": "string"
                          },
                          "project_type": {
                            "type": "string",
                            "enum": [
                              "JALAN",
                              "DRAINASE",
                              "GEDUNG"
                            ]
                          },
                          "department_name": {
                            "type": "string"
                          },
                          "location": {
                            "type": "object",
                            "properties": {
                              "lat": {
                                "type": "number",
                                "minimum": -90,
                                "maximum": 90
                              },
                              "lng": {
                                "type": "number",
                                "minimum": -180,
                                "maximum": 180
                              }
                            },
                            "required": [
                              "lat",
                              "lng"
                            ],
                            "additionalProperties": false,
                            "description": "Koordinat WGS84"
                          },
                          "address": {
                            "type": "string"
                          },
                          "pagu": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Pagu anggaran paket"
                          },
                          "hps_total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Total Harga Perkiraan Sendiri"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "DRAFT",
                              "DIUMUMKAN",
                              "PENAWARAN",
                              "EVALUASI",
                              "PENETAPAN",
                              "SANGGAH",
                              "KONTRAK",
                              "PELAKSANAAN",
                              "SELESAI",
                              "DIBATALKAN"
                            ]
                          },
                          "risk_score": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 100
                          },
                          "risk_level": {
                            "type": "string",
                            "enum": [
                              "LOW",
                              "MEDIUM",
                              "HIGH"
                            ]
                          },
                          "bid_count": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Jumlah penawaran; tidak menampilkan harga sebelum penetapan"
                          },
                          "citizen_report_count": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "is_demo": {
                            "type": "boolean"
                          },
                          "announced_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "bid_close_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          }
                        },
                        "required": [
                          "id",
                          "code",
                          "title",
                          "project_type",
                          "department_name",
                          "location",
                          "pagu",
                          "hps_total",
                          "status",
                          "risk_score",
                          "risk_level",
                          "bid_count",
                          "citizen_report_count",
                          "is_demo",
                          "announced_at",
                          "bid_close_at",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "next_cursor": {
                      "description": "Cursor halaman berikutnya; null bila sudah habis",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "packages.create",
        "summary": "PPK membuat paket (draf)",
        "description": "Membuat paket berstatus `DRAFT`. Paket belum terlihat publik sampai diumumkan Pokja.\n\nNilai `pagu` dikunci sejak dibuat dan tidak dapat diubah setelah pengumuman. Aksi tercatat di `audit_logs` (append-only, hash-chain) dengan `actor_role` pemanggil.",
        "tags": [
          "PPK — Paket & HPS"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 300
                  },
                  "project_type": {
                    "type": "string",
                    "enum": [
                      "JALAN",
                      "DRAINASE",
                      "GEDUNG"
                    ]
                  },
                  "department_name": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 5000
                  },
                  "location": {
                    "type": "object",
                    "properties": {
                      "lat": {
                        "type": "number",
                        "minimum": -90,
                        "maximum": 90
                      },
                      "lng": {
                        "type": "number",
                        "minimum": -180,
                        "maximum": 180
                      }
                    },
                    "required": [
                      "lat",
                      "lng"
                    ],
                    "description": "Koordinat WGS84"
                  },
                  "address": {
                    "type": "string",
                    "maxLength": 300
                  },
                  "pagu": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991,
                    "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                  },
                  "schedule": {
                    "type": "object",
                    "properties": {
                      "announcement": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "bid_open": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "bid_close": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "evaluation": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "award": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "appeal_end": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "contract": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "title",
                  "project_type",
                  "department_name",
                  "location",
                  "pagu"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Paket dibuat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "code": {
                      "type": "string",
                      "description": "Kode paket/RUP demo, mis. DEMO-RUP-DRN-01"
                    },
                    "title": {
                      "type": "string"
                    },
                    "project_type": {
                      "type": "string",
                      "enum": [
                        "JALAN",
                        "DRAINASE",
                        "GEDUNG"
                      ]
                    },
                    "department_name": {
                      "type": "string"
                    },
                    "location": {
                      "type": "object",
                      "properties": {
                        "lat": {
                          "type": "number",
                          "minimum": -90,
                          "maximum": 90
                        },
                        "lng": {
                          "type": "number",
                          "minimum": -180,
                          "maximum": 180
                        }
                      },
                      "required": [
                        "lat",
                        "lng"
                      ],
                      "additionalProperties": false,
                      "description": "Koordinat WGS84"
                    },
                    "address": {
                      "type": "string"
                    },
                    "pagu": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Pagu anggaran paket"
                    },
                    "hps_total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Total Harga Perkiraan Sendiri"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "DRAFT",
                        "DIUMUMKAN",
                        "PENAWARAN",
                        "EVALUASI",
                        "PENETAPAN",
                        "SANGGAH",
                        "KONTRAK",
                        "PELAKSANAAN",
                        "SELESAI",
                        "DIBATALKAN"
                      ]
                    },
                    "risk_score": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    },
                    "risk_level": {
                      "type": "string",
                      "enum": [
                        "LOW",
                        "MEDIUM",
                        "HIGH"
                      ]
                    },
                    "bid_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Jumlah penawaran; tidak menampilkan harga sebelum penetapan"
                    },
                    "citizen_report_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "is_demo": {
                      "type": "boolean"
                    },
                    "announced_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "bid_close_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "description": {
                      "type": "string"
                    },
                    "ppk_name": {
                      "type": "string"
                    },
                    "pokja_name": {
                      "type": "string"
                    },
                    "ai_summary": {
                      "description": "Ringkasan bahasa awam; null bila belum dibuat",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "flags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "INFO",
                              "WARNING",
                              "CRITICAL"
                            ]
                          },
                          "message": {
                            "type": "string",
                            "description": "Penjelasan bahasa awam"
                          },
                          "subject": {
                            "description": "Item/entitas yang ditandai",
                            "type": "string"
                          },
                          "value": {
                            "description": "Angka pendukung, mis. persentase di atas acuan",
                            "type": "number"
                          }
                        },
                        "required": [
                          "code",
                          "severity",
                          "message"
                        ],
                        "additionalProperties": false,
                        "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                      },
                      "description": "Indikator tingkat paket (HPS, penawar tunggal, rasio penawaran, dll.)"
                    },
                    "schedule": {
                      "type": "object",
                      "properties": {
                        "announcement": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_open": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_close": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "evaluation": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "award": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "appeal_end": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "contract": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "announcement",
                        "bid_open",
                        "bid_close",
                        "evaluation",
                        "award",
                        "appeal_end",
                        "contract"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "id",
                    "code",
                    "title",
                    "project_type",
                    "department_name",
                    "location",
                    "pagu",
                    "hps_total",
                    "status",
                    "risk_score",
                    "risk_level",
                    "bid_count",
                    "citizen_report_count",
                    "is_demo",
                    "announced_at",
                    "bid_close_at",
                    "created_at",
                    "ai_summary",
                    "flags",
                    "schedule"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}": {
      "get": {
        "operationId": "packages.get",
        "summary": "Detail satu paket",
        "description": "Detail paket beserta jadwal, indikator tingkat paket, dan ringkasan bahasa awam.\n\nBila `status` sudah melewati `PENETAPAN`, respons menyertakan `award` dan rincian penawaran pemenang pada endpoint terpisah (`packages.results`).",
        "tags": [
          "Publik"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Detail paket",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "code": {
                      "type": "string",
                      "description": "Kode paket/RUP demo, mis. DEMO-RUP-DRN-01"
                    },
                    "title": {
                      "type": "string"
                    },
                    "project_type": {
                      "type": "string",
                      "enum": [
                        "JALAN",
                        "DRAINASE",
                        "GEDUNG"
                      ]
                    },
                    "department_name": {
                      "type": "string"
                    },
                    "location": {
                      "type": "object",
                      "properties": {
                        "lat": {
                          "type": "number",
                          "minimum": -90,
                          "maximum": 90
                        },
                        "lng": {
                          "type": "number",
                          "minimum": -180,
                          "maximum": 180
                        }
                      },
                      "required": [
                        "lat",
                        "lng"
                      ],
                      "additionalProperties": false,
                      "description": "Koordinat WGS84"
                    },
                    "address": {
                      "type": "string"
                    },
                    "pagu": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Pagu anggaran paket"
                    },
                    "hps_total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Total Harga Perkiraan Sendiri"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "DRAFT",
                        "DIUMUMKAN",
                        "PENAWARAN",
                        "EVALUASI",
                        "PENETAPAN",
                        "SANGGAH",
                        "KONTRAK",
                        "PELAKSANAAN",
                        "SELESAI",
                        "DIBATALKAN"
                      ]
                    },
                    "risk_score": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    },
                    "risk_level": {
                      "type": "string",
                      "enum": [
                        "LOW",
                        "MEDIUM",
                        "HIGH"
                      ]
                    },
                    "bid_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Jumlah penawaran; tidak menampilkan harga sebelum penetapan"
                    },
                    "citizen_report_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "is_demo": {
                      "type": "boolean"
                    },
                    "announced_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "bid_close_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "description": {
                      "type": "string"
                    },
                    "ppk_name": {
                      "type": "string"
                    },
                    "pokja_name": {
                      "type": "string"
                    },
                    "ai_summary": {
                      "description": "Ringkasan bahasa awam; null bila belum dibuat",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "flags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "INFO",
                              "WARNING",
                              "CRITICAL"
                            ]
                          },
                          "message": {
                            "type": "string",
                            "description": "Penjelasan bahasa awam"
                          },
                          "subject": {
                            "description": "Item/entitas yang ditandai",
                            "type": "string"
                          },
                          "value": {
                            "description": "Angka pendukung, mis. persentase di atas acuan",
                            "type": "number"
                          }
                        },
                        "required": [
                          "code",
                          "severity",
                          "message"
                        ],
                        "additionalProperties": false,
                        "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                      },
                      "description": "Indikator tingkat paket (HPS, penawar tunggal, rasio penawaran, dll.)"
                    },
                    "schedule": {
                      "type": "object",
                      "properties": {
                        "announcement": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_open": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_close": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "evaluation": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "award": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "appeal_end": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "contract": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "announcement",
                        "bid_open",
                        "bid_close",
                        "evaluation",
                        "award",
                        "appeal_end",
                        "contract"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "id",
                    "code",
                    "title",
                    "project_type",
                    "department_name",
                    "location",
                    "pagu",
                    "hps_total",
                    "status",
                    "risk_score",
                    "risk_level",
                    "bid_count",
                    "citizen_report_count",
                    "is_demo",
                    "announced_at",
                    "bid_close_at",
                    "created_at",
                    "ai_summary",
                    "flags",
                    "schedule"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "packages.update",
        "summary": "PPK mengubah paket (hanya DRAFT)",
        "description": "Hanya berlaku saat `status = DRAFT`. Upaya mengubah setelah `DIUMUMKAN` menghasilkan `409 STATE_CONFLICT`.\n\nField `status` tidak dapat diubah lewat PATCH; gunakan endpoint transisi (`announce`, `submit`, dst.).",
        "tags": [
          "PPK — Paket & HPS"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 300
                  },
                  "project_type": {
                    "type": "string",
                    "enum": [
                      "JALAN",
                      "DRAINASE",
                      "GEDUNG"
                    ]
                  },
                  "department_name": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 5000
                  },
                  "location": {
                    "type": "object",
                    "properties": {
                      "lat": {
                        "type": "number",
                        "minimum": -90,
                        "maximum": 90
                      },
                      "lng": {
                        "type": "number",
                        "minimum": -180,
                        "maximum": 180
                      }
                    },
                    "required": [
                      "lat",
                      "lng"
                    ],
                    "description": "Koordinat WGS84"
                  },
                  "address": {
                    "type": "string",
                    "maxLength": 300
                  },
                  "pagu": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991,
                    "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                  },
                  "schedule": {
                    "type": "object",
                    "properties": {
                      "announcement": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "bid_open": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "bid_close": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "evaluation": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "award": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "appeal_end": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "contract": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      }
                    }
                  },
                  "status": {
                    "description": "Status tidak bisa diubah lewat PATCH; gunakan endpoint transisi",
                    "not": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paket terbaru",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "code": {
                      "type": "string",
                      "description": "Kode paket/RUP demo, mis. DEMO-RUP-DRN-01"
                    },
                    "title": {
                      "type": "string"
                    },
                    "project_type": {
                      "type": "string",
                      "enum": [
                        "JALAN",
                        "DRAINASE",
                        "GEDUNG"
                      ]
                    },
                    "department_name": {
                      "type": "string"
                    },
                    "location": {
                      "type": "object",
                      "properties": {
                        "lat": {
                          "type": "number",
                          "minimum": -90,
                          "maximum": 90
                        },
                        "lng": {
                          "type": "number",
                          "minimum": -180,
                          "maximum": 180
                        }
                      },
                      "required": [
                        "lat",
                        "lng"
                      ],
                      "additionalProperties": false,
                      "description": "Koordinat WGS84"
                    },
                    "address": {
                      "type": "string"
                    },
                    "pagu": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Pagu anggaran paket"
                    },
                    "hps_total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Total Harga Perkiraan Sendiri"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "DRAFT",
                        "DIUMUMKAN",
                        "PENAWARAN",
                        "EVALUASI",
                        "PENETAPAN",
                        "SANGGAH",
                        "KONTRAK",
                        "PELAKSANAAN",
                        "SELESAI",
                        "DIBATALKAN"
                      ]
                    },
                    "risk_score": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    },
                    "risk_level": {
                      "type": "string",
                      "enum": [
                        "LOW",
                        "MEDIUM",
                        "HIGH"
                      ]
                    },
                    "bid_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Jumlah penawaran; tidak menampilkan harga sebelum penetapan"
                    },
                    "citizen_report_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "is_demo": {
                      "type": "boolean"
                    },
                    "announced_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "bid_close_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "description": {
                      "type": "string"
                    },
                    "ppk_name": {
                      "type": "string"
                    },
                    "pokja_name": {
                      "type": "string"
                    },
                    "ai_summary": {
                      "description": "Ringkasan bahasa awam; null bila belum dibuat",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "flags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "INFO",
                              "WARNING",
                              "CRITICAL"
                            ]
                          },
                          "message": {
                            "type": "string",
                            "description": "Penjelasan bahasa awam"
                          },
                          "subject": {
                            "description": "Item/entitas yang ditandai",
                            "type": "string"
                          },
                          "value": {
                            "description": "Angka pendukung, mis. persentase di atas acuan",
                            "type": "number"
                          }
                        },
                        "required": [
                          "code",
                          "severity",
                          "message"
                        ],
                        "additionalProperties": false,
                        "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                      },
                      "description": "Indikator tingkat paket (HPS, penawar tunggal, rasio penawaran, dll.)"
                    },
                    "schedule": {
                      "type": "object",
                      "properties": {
                        "announcement": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_open": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_close": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "evaluation": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "award": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "appeal_end": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "contract": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "announcement",
                        "bid_open",
                        "bid_close",
                        "evaluation",
                        "award",
                        "appeal_end",
                        "contract"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "id",
                    "code",
                    "title",
                    "project_type",
                    "department_name",
                    "location",
                    "pagu",
                    "hps_total",
                    "status",
                    "risk_score",
                    "risk_level",
                    "bid_count",
                    "citizen_report_count",
                    "is_demo",
                    "announced_at",
                    "bid_close_at",
                    "created_at",
                    "ai_summary",
                    "flags",
                    "schedule"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/stats": {
      "get": {
        "operationId": "packages.stats",
        "summary": "Statistik ringkas untuk beranda",
        "description": "Agregat publik: jumlah paket, total nilai, jumlah yang perlu klarifikasi, jumlah item ditandai, dan potensi selisih.",
        "tags": [
          "Publik"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "responses": {
          "200": {
            "description": "Ringkasan agregat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "packages": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "contract_total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "flagged_packages": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "no_bid_packages": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "flagged_items": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "exposure": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Total potensi selisih harga yang perlu dijelaskan"
                    }
                  },
                  "required": [
                    "packages",
                    "contract_total",
                    "flagged_packages",
                    "no_bid_packages",
                    "flagged_items",
                    "exposure"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/bids/commitments": {
      "get": {
        "operationId": "packages.commitments",
        "summary": "Hash komitmen penawaran (tanpa harga)",
        "description": "Selama masa penawaran, publik hanya melihat **bukti komitmen**: nama samaran peserta, SHA-256 isi penawaran, waktu kirim, dan nomor revisi.\n\nHash dihitung atas isi penawaran yang tersegel. Setelah pembukaan, hash dapat diverifikasi ulang; perubahan satu angka akan mengubah hash.\n\nEndpoint ini sengaja tidak mengembalikan harga, total, atau identitas penyedia.",
        "tags": [
          "Publik",
          "Penyedia — Penawaran"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daftar komitmen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "alias": {
                            "type": "string",
                            "description": "Nama samaran peserta sebelum pembukaan, mis. Peserta 3"
                          },
                          "commit_hash": {
                            "type": "string",
                            "pattern": "^[0-9a-f]{64}$",
                            "description": "SHA-256 heksadesimal 64 karakter"
                          },
                          "submitted_at": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          "revision": {
                            "type": "integer",
                            "minimum": -9007199254740991,
                            "maximum": 9007199254740991
                          }
                        },
                        "required": [
                          "alias",
                          "commit_hash",
                          "submitted_at",
                          "revision"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "next_cursor": {
                      "description": "Cursor halaman berikutnya; null bila sudah habis",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/results": {
      "get": {
        "operationId": "packages.results",
        "summary": "Hasil penetapan dan perbandingan penawaran",
        "description": "Dibuka **setelah** penetapan pemenang dan masa sanggah berakhir, sesuai keputusan K-13.\n\nBerisi pemenang, nilai kontrak, serta perbandingan side-by-side terhadap HPS dan HSPK: total tiap peserta, rasio terhadap HPS, flag harga per item, dan indikator kemiripan antar-penawar.\n\nIdentitas badan usaha yang sensitif (NPWP, alamat, kontak) tidak ditampilkan. Sebelum penetapan, endpoint mengembalikan `409 STATE_CONFLICT`.",
        "tags": [
          "Publik",
          "Pokja — Evaluasi & Penetapan"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hasil penetapan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "bid_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "provider": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string",
                          "description": "Nama penyedia demo (fiktif)"
                        },
                        "integrity_score": {
                          "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ],
                      "additionalProperties": false
                    },
                    "value": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nilai penawaran pemenang (menjadi nilai kontrak)"
                    },
                    "decided_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "rank": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "not_lowest_reason": {
                      "description": "Wajib bila pemenang bukan peringkat 1",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "appeal_end_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "entries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bid_id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "alias": {
                            "type": "string"
                          },
                          "provider": {
                            "description": "Terlihat oleh Pokja/Auditor; publik hanya setelah penetapan",
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "name": {
                                "type": "string",
                                "description": "Nama penyedia demo (fiktif)"
                              },
                              "integrity_score": {
                                "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 100
                              }
                            },
                            "required": [
                              "id",
                              "name"
                            ],
                            "additionalProperties": false
                          },
                          "total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "corrected_total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Total setelah koreksi aritmetika"
                          },
                          "hps_ratio_pct": {
                            "type": "number",
                            "description": "Total penawaran ÷ HPS × 100"
                          },
                          "total_synced": {
                            "type": "boolean"
                          },
                          "passed": {
                            "type": "boolean"
                          },
                          "rank": {
                            "anyOf": [
                              {
                                "type": "integer",
                                "minimum": -9007199254740991,
                                "maximum": 9007199254740991
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Peringkat harga terkoreksi terendah di antara yang lulus"
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string",
                                  "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "INFO",
                                    "WARNING",
                                    "CRITICAL"
                                  ]
                                },
                                "message": {
                                  "type": "string",
                                  "description": "Penjelasan bahasa awam"
                                },
                                "subject": {
                                  "description": "Item/entitas yang ditandai",
                                  "type": "string"
                                },
                                "value": {
                                  "description": "Angka pendukung, mis. persentase di atas acuan",
                                  "type": "number"
                                }
                              },
                              "required": [
                                "code",
                                "severity",
                                "message"
                              ],
                              "additionalProperties": false,
                              "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                            }
                          },
                          "similarity": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "peer_alias": {
                                  "type": "string"
                                },
                                "identical_item_pct": {
                                  "type": "number",
                                  "description": "Persentase item dengan harga satuan identik/hampir identik (±0,5%)"
                                },
                                "price_order_correlation": {
                                  "type": "number",
                                  "minimum": -1,
                                  "maximum": 1,
                                  "description": "Korelasi urutan harga antar-penawar"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "LOW",
                                    "MEDIUM",
                                    "HIGH"
                                  ]
                                }
                              },
                              "required": [
                                "peer_alias",
                                "identical_item_pct",
                                "price_order_correlation",
                                "severity"
                              ],
                              "additionalProperties": false
                            },
                            "description": "Kemiripan dengan penawar lain"
                          }
                        },
                        "required": [
                          "bid_id",
                          "alias",
                          "total",
                          "corrected_total",
                          "hps_ratio_pct",
                          "total_synced",
                          "passed",
                          "rank",
                          "flags",
                          "similarity"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Perbandingan seluruh peserta yang lulus"
                    }
                  },
                  "required": [
                    "package_id",
                    "bid_id",
                    "provider",
                    "value",
                    "decided_at",
                    "rank",
                    "not_lowest_reason",
                    "appeal_end_at",
                    "entries"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/reports": {
      "get": {
        "operationId": "packages.reports",
        "summary": "Laporan warga pada satu paket",
        "description": "Laporan lapangan yang terhubung ke paket, termasuk laporan gabungan (cluster) dalam radius 50 m.\n\nFoto publik sudah bebas EXIF; koordinat dibulatkan 4 desimal; tidak ada identitas pelapor.",
        "tags": [
          "Publik",
          "Laporan Warga"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          },
          {
            "name": "package_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "PENDING",
                "VERIFIED",
                "RESOLVED",
                "REJECTED"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daftar laporan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "package_id": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "null bila tidak ada paket dalam radius 50 m"
                          },
                          "report_type": {
                            "type": "string",
                            "enum": [
                              "PHYSICAL_ISSUE",
                              "BOARD_MISMATCH",
                              "STALLED_PROJECT"
                            ]
                          },
                          "description": {
                            "type": "string"
                          },
                          "photo_url": {
                            "type": "string",
                            "description": "URL publik; metadata EXIF sudah dihapus"
                          },
                          "location": {
                            "type": "object",
                            "properties": {
                              "lat": {
                                "type": "number",
                                "minimum": -90,
                                "maximum": 90
                              },
                              "lng": {
                                "type": "number",
                                "minimum": -180,
                                "maximum": 180
                              }
                            },
                            "required": [
                              "lat",
                              "lng"
                            ],
                            "additionalProperties": false,
                            "description": "Titik kejadian; dibulatkan 4 desimal pada respons publik"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "PENDING",
                              "VERIFIED",
                              "RESOLVED",
                              "REJECTED"
                            ]
                          },
                          "cluster_id": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Laporan ganda dalam 50 m digabung, aslinya tetap tersimpan"
                          },
                          "duplicate_of": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "vision": {
                            "anyOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "confidence": {
                                    "type": "number",
                                    "minimum": 0,
                                    "maximum": 1
                                  },
                                  "matches_category": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "label",
                                  "confidence",
                                  "matches_category"
                                ],
                                "additionalProperties": false
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Indikator bantu dari model visi, bukan penentu"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          }
                        },
                        "required": [
                          "id",
                          "package_id",
                          "report_type",
                          "description",
                          "photo_url",
                          "location",
                          "status",
                          "cluster_id",
                          "duplicate_of",
                          "vision",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "next_cursor": {
                      "description": "Cursor halaman berikutnya; null bila sudah habis",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/price-references": {
      "get": {
        "operationId": "price-references.list",
        "summary": "Katalog harga acuan HSPK",
        "description": "396 item harga acuan dari Perwali Kota Malang No. 10/2022 (HSPK 2022) untuk jenis Jalan, Drainase, dan Gedung.\n\nDipakai form HPS (PPK) dan form penawaran (Penyedia) untuk memilih `ssh_code`. Parameter `q` mencari pada nama item; `category` menyaring jenis.",
        "tags": [
          "Publik",
          "Referensi"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "JALAN",
                "DRAINASE",
                "GEDUNG",
                "UMUM"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 20,
              "description": "Jumlah item per halaman",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "description": "Cursor dari respons sebelumnya",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daftar harga acuan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "item_name": {
                            "type": "string"
                          },
                          "unit": {
                            "type": "string"
                          },
                          "price": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Harga satuan HSPK 2022"
                          },
                          "category": {
                            "type": "string"
                          },
                          "source": {
                            "type": "string",
                            "description": "Perwali Kota Malang No. 10/2022"
                          }
                        },
                        "required": [
                          "code",
                          "item_name",
                          "unit",
                          "price",
                          "category",
                          "source"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "next_cursor": {
                      "description": "Cursor halaman berikutnya; null bila sudah habis",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/audit/chain": {
      "get": {
        "operationId": "audit.chain",
        "summary": "Verifikasi rantai audit",
        "description": "Menjalankan `verify_audit_chain()` dan mengembalikan hasilnya: utuh atau rusak pada baris tertentu.\n\nHash-chain bersifat **tamper-evident**, bukan tamper-proof: perubahan dapat dideteksi, bukan dicegah secara absolut.",
        "tags": [
          "Publik",
          "Sistem"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "responses": {
          "200": {
            "description": "Status rantai audit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "intact": {
                      "type": "boolean"
                    },
                    "broken_at": {
                      "description": "ID entri pertama yang gagal diverifikasi",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "checked_rows": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "checked_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    }
                  },
                  "required": [
                    "intact",
                    "broken_at",
                    "checked_rows",
                    "checked_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/audit/log": {
      "get": {
        "operationId": "audit.log",
        "summary": "Log audit publik (read-only)",
        "description": "Riwayat aksi pada seluruh entitas, tanpa data sensitif. Filter `entity_type` dan `entity_id`.",
        "tags": [
          "Publik",
          "Sistem"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "parameters": [
          {
            "name": "entity_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "entity_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entri audit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "action": {
                            "type": "string"
                          },
                          "actor_role": {
                            "type": "string",
                            "enum": [
                              "PUBLIK",
                              "PENYEDIA",
                              "POKJA",
                              "PPK",
                              "AUDITOR",
                              "ADMIN"
                            ]
                          },
                          "actor_name": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          "previous_state": {
                            "type": "string",
                            "format": "binary"
                          },
                          "new_state": {
                            "type": "string",
                            "format": "binary"
                          }
                        },
                        "required": [
                          "id",
                          "action",
                          "actor_role",
                          "created_at"
                        ],
                        "additionalProperties": false,
                        "description": "Satu baris audit log ber-hash-chain"
                      }
                    },
                    "next_cursor": {
                      "description": "Cursor halaman berikutnya; null bila sudah habis",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/hps": {
      "get": {
        "operationId": "packages.hps.list",
        "summary": "Susunan HPS paket",
        "description": "Daftar item HPS beserta perbandingan terhadap HSPK dan totalnya.",
        "tags": [
          "PPK — Paket & HPS"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "POKJA",
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshot HPS",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "item_count": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "against_hspk": {
                      "type": "object",
                      "properties": {
                        "flagged_count": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        },
                        "exposure": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Total potensi selisih HPS terhadap HSPK"
                        },
                        "max_markup_pct": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "flagged_count",
                        "exposure",
                        "max_markup_pct"
                      ],
                      "additionalProperties": false
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "item_name": {
                            "type": "string"
                          },
                          "unit": {
                            "type": "string",
                            "description": "Satuan setelah normalisasi, mis. m2"
                          },
                          "volume": {
                            "type": "number",
                            "exclusiveMinimum": 0
                          },
                          "unit_price": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Harga satuan di HPS"
                          },
                          "amount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "volume × unit_price"
                          },
                          "ssh_code": {
                            "description": "Kode HSPK acuan; null bila item manual",
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "ssh_price": {
                            "anyOf": [
                              {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "markup_pct": {
                            "description": "Selisih harga HPS terhadap HSPK",
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string",
                                  "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "INFO",
                                    "WARNING",
                                    "CRITICAL"
                                  ]
                                },
                                "message": {
                                  "type": "string",
                                  "description": "Penjelasan bahasa awam"
                                },
                                "subject": {
                                  "description": "Item/entitas yang ditandai",
                                  "type": "string"
                                },
                                "value": {
                                  "description": "Angka pendukung, mis. persentase di atas acuan",
                                  "type": "number"
                                }
                              },
                              "required": [
                                "code",
                                "severity",
                                "message"
                              ],
                              "additionalProperties": false,
                              "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                            }
                          }
                        },
                        "required": [
                          "id",
                          "item_name",
                          "unit",
                          "volume",
                          "unit_price",
                          "amount",
                          "ssh_code",
                          "ssh_price",
                          "markup_pct",
                          "flags"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "package_id",
                    "total",
                    "item_count",
                    "against_hspk"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/hps/items": {
      "post": {
        "operationId": "packages.hps.add",
        "summary": "Tambah item HPS",
        "description": "Item boleh berasal dari katalog HSPK (`ssh_code`) atau manual (`item_name` + `unit`).\n\nSistem langsung menghitung `amount`, `markup_pct`, dan flag bila harga di atas ambang 20% — peringatan dini sebelum paket diumumkan.\n\nSatuan dinormalisasi (`m2`, `m3`, `m`, `unit`, …). Satuan yang tidak sepadan dengan HSPK tidak dibandingkan, hanya diberi warning.",
        "tags": [
          "PPK — Paket & HPS"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ssh_code": {
                    "description": "Pilih dari katalog HSPK",
                    "type": "string"
                  },
                  "item_name": {
                    "description": "Wajib bila ssh_code kosong",
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 300
                  },
                  "unit": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20
                  },
                  "volume": {
                    "type": "number",
                    "exclusiveMinimum": 0
                  },
                  "unit_price": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991,
                    "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                  }
                },
                "required": [
                  "volume",
                  "unit_price"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Item ditambahkan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "item_name": {
                      "type": "string"
                    },
                    "unit": {
                      "type": "string",
                      "description": "Satuan setelah normalisasi, mis. m2"
                    },
                    "volume": {
                      "type": "number",
                      "exclusiveMinimum": 0
                    },
                    "unit_price": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Harga satuan di HPS"
                    },
                    "amount": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "volume × unit_price"
                    },
                    "ssh_code": {
                      "description": "Kode HSPK acuan; null bila item manual",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ssh_price": {
                      "anyOf": [
                        {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "markup_pct": {
                      "description": "Selisih harga HPS terhadap HSPK",
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "flags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "INFO",
                              "WARNING",
                              "CRITICAL"
                            ]
                          },
                          "message": {
                            "type": "string",
                            "description": "Penjelasan bahasa awam"
                          },
                          "subject": {
                            "description": "Item/entitas yang ditandai",
                            "type": "string"
                          },
                          "value": {
                            "description": "Angka pendukung, mis. persentase di atas acuan",
                            "type": "number"
                          }
                        },
                        "required": [
                          "code",
                          "severity",
                          "message"
                        ],
                        "additionalProperties": false,
                        "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                      }
                    }
                  },
                  "required": [
                    "id",
                    "item_name",
                    "unit",
                    "volume",
                    "unit_price",
                    "amount",
                    "ssh_code",
                    "ssh_price",
                    "markup_pct",
                    "flags"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/hps/items/{itemId}": {
      "patch": {
        "operationId": "packages.hps.update",
        "summary": "Ubah item HPS",
        "description": "Mengubah volume/harga item HPS selama paket masih `DRAFT`. Total dan flag dihitung ulang.",
        "tags": [
          "PPK — Paket & HPS"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ssh_code": {
                    "description": "Pilih dari katalog HSPK",
                    "type": "string"
                  },
                  "item_name": {
                    "description": "Wajib bila ssh_code kosong",
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 300
                  },
                  "unit": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20
                  },
                  "volume": {
                    "type": "number",
                    "exclusiveMinimum": 0
                  },
                  "unit_price": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991,
                    "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Item terbaru",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "item_name": {
                      "type": "string"
                    },
                    "unit": {
                      "type": "string",
                      "description": "Satuan setelah normalisasi, mis. m2"
                    },
                    "volume": {
                      "type": "number",
                      "exclusiveMinimum": 0
                    },
                    "unit_price": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Harga satuan di HPS"
                    },
                    "amount": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "volume × unit_price"
                    },
                    "ssh_code": {
                      "description": "Kode HSPK acuan; null bila item manual",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ssh_price": {
                      "anyOf": [
                        {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "markup_pct": {
                      "description": "Selisih harga HPS terhadap HSPK",
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "flags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "INFO",
                              "WARNING",
                              "CRITICAL"
                            ]
                          },
                          "message": {
                            "type": "string",
                            "description": "Penjelasan bahasa awam"
                          },
                          "subject": {
                            "description": "Item/entitas yang ditandai",
                            "type": "string"
                          },
                          "value": {
                            "description": "Angka pendukung, mis. persentase di atas acuan",
                            "type": "number"
                          }
                        },
                        "required": [
                          "code",
                          "severity",
                          "message"
                        ],
                        "additionalProperties": false,
                        "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                      }
                    }
                  },
                  "required": [
                    "id",
                    "item_name",
                    "unit",
                    "volume",
                    "unit_price",
                    "amount",
                    "ssh_code",
                    "ssh_price",
                    "markup_pct",
                    "flags"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "packages.hps.remove",
        "summary": "Hapus item HPS",
        "description": "Menghapus item HPS selama paket masih `DRAFT`. Penghapusan dicatat di audit log.",
        "tags": [
          "PPK — Paket & HPS"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Item dihapus"
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/hps/preview": {
      "post": {
        "operationId": "packages.hps.preview",
        "summary": "Pratinjau pemeriksaan HPS",
        "description": "Menjalankan pemeriksaan yang sama dengan saat pengumuman, tanpa menyimpan apa pun: total HPS, jumlah item di atas ambang, potensi selisih, dan flag per item.\n\nBerguna untuk PPK sebelum mengajukan paket ke Pokja.",
        "tags": [
          "PPK — Paket & HPS"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hasil pemeriksaan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "item_count": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "against_hspk": {
                      "type": "object",
                      "properties": {
                        "flagged_count": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        },
                        "exposure": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Total potensi selisih HPS terhadap HSPK"
                        },
                        "max_markup_pct": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "flagged_count",
                        "exposure",
                        "max_markup_pct"
                      ],
                      "additionalProperties": false
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "item_name": {
                            "type": "string"
                          },
                          "unit": {
                            "type": "string",
                            "description": "Satuan setelah normalisasi, mis. m2"
                          },
                          "volume": {
                            "type": "number",
                            "exclusiveMinimum": 0
                          },
                          "unit_price": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Harga satuan di HPS"
                          },
                          "amount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "volume × unit_price"
                          },
                          "ssh_code": {
                            "description": "Kode HSPK acuan; null bila item manual",
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "ssh_price": {
                            "anyOf": [
                              {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "markup_pct": {
                            "description": "Selisih harga HPS terhadap HSPK",
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string",
                                  "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "INFO",
                                    "WARNING",
                                    "CRITICAL"
                                  ]
                                },
                                "message": {
                                  "type": "string",
                                  "description": "Penjelasan bahasa awam"
                                },
                                "subject": {
                                  "description": "Item/entitas yang ditandai",
                                  "type": "string"
                                },
                                "value": {
                                  "description": "Angka pendukung, mis. persentase di atas acuan",
                                  "type": "number"
                                }
                              },
                              "required": [
                                "code",
                                "severity",
                                "message"
                              ],
                              "additionalProperties": false,
                              "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                            }
                          }
                        },
                        "required": [
                          "id",
                          "item_name",
                          "unit",
                          "volume",
                          "unit_price",
                          "amount",
                          "ssh_code",
                          "ssh_price",
                          "markup_pct",
                          "flags"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "package_id",
                    "total",
                    "item_count",
                    "against_hspk"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/submit": {
      "post": {
        "operationId": "packages.submit",
        "summary": "PPK mengajukan paket ke Pokja",
        "description": "Transisi `DRAFT → DIUMUMKAN` (menunggu tindakan Pokja). Syarat: minimal satu item HPS dan total HPS > 0.\n\nSetelah diajukan, HPS dan pagu terkunci; perubahan hanya lewat pembatalan paket yang tercatat.",
        "tags": [
          "PPK — Paket & HPS"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paket diajukan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "code": {
                      "type": "string",
                      "description": "Kode paket/RUP demo, mis. DEMO-RUP-DRN-01"
                    },
                    "title": {
                      "type": "string"
                    },
                    "project_type": {
                      "type": "string",
                      "enum": [
                        "JALAN",
                        "DRAINASE",
                        "GEDUNG"
                      ]
                    },
                    "department_name": {
                      "type": "string"
                    },
                    "location": {
                      "type": "object",
                      "properties": {
                        "lat": {
                          "type": "number",
                          "minimum": -90,
                          "maximum": 90
                        },
                        "lng": {
                          "type": "number",
                          "minimum": -180,
                          "maximum": 180
                        }
                      },
                      "required": [
                        "lat",
                        "lng"
                      ],
                      "additionalProperties": false,
                      "description": "Koordinat WGS84"
                    },
                    "address": {
                      "type": "string"
                    },
                    "pagu": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Pagu anggaran paket"
                    },
                    "hps_total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Total Harga Perkiraan Sendiri"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "DRAFT",
                        "DIUMUMKAN",
                        "PENAWARAN",
                        "EVALUASI",
                        "PENETAPAN",
                        "SANGGAH",
                        "KONTRAK",
                        "PELAKSANAAN",
                        "SELESAI",
                        "DIBATALKAN"
                      ]
                    },
                    "risk_score": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    },
                    "risk_level": {
                      "type": "string",
                      "enum": [
                        "LOW",
                        "MEDIUM",
                        "HIGH"
                      ]
                    },
                    "bid_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Jumlah penawaran; tidak menampilkan harga sebelum penetapan"
                    },
                    "citizen_report_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "is_demo": {
                      "type": "boolean"
                    },
                    "announced_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "bid_close_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "description": {
                      "type": "string"
                    },
                    "ppk_name": {
                      "type": "string"
                    },
                    "pokja_name": {
                      "type": "string"
                    },
                    "ai_summary": {
                      "description": "Ringkasan bahasa awam; null bila belum dibuat",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "flags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "INFO",
                              "WARNING",
                              "CRITICAL"
                            ]
                          },
                          "message": {
                            "type": "string",
                            "description": "Penjelasan bahasa awam"
                          },
                          "subject": {
                            "description": "Item/entitas yang ditandai",
                            "type": "string"
                          },
                          "value": {
                            "description": "Angka pendukung, mis. persentase di atas acuan",
                            "type": "number"
                          }
                        },
                        "required": [
                          "code",
                          "severity",
                          "message"
                        ],
                        "additionalProperties": false,
                        "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                      },
                      "description": "Indikator tingkat paket (HPS, penawar tunggal, rasio penawaran, dll.)"
                    },
                    "schedule": {
                      "type": "object",
                      "properties": {
                        "announcement": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_open": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_close": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "evaluation": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "award": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "appeal_end": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "contract": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "announcement",
                        "bid_open",
                        "bid_close",
                        "evaluation",
                        "award",
                        "appeal_end",
                        "contract"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "id",
                    "code",
                    "title",
                    "project_type",
                    "department_name",
                    "location",
                    "pagu",
                    "hps_total",
                    "status",
                    "risk_score",
                    "risk_level",
                    "bid_count",
                    "citizen_report_count",
                    "is_demo",
                    "announced_at",
                    "bid_close_at",
                    "created_at",
                    "ai_summary",
                    "flags",
                    "schedule"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/contract": {
      "post": {
        "operationId": "contracts.create",
        "summary": "PPK menetapkan kontrak dan jadwal termin",
        "description": "Dibuat setelah penetapan pemenang dan masa sanggah berakhir. `value` wajib sama dengan nilai penawaran pemenang; selisih sekecil apa pun ditolak `422`.\n\nJumlah seluruh termin tidak boleh melebihi nilai kontrak. Termin pertama biasanya uang muka/termin awal.",
        "tags": [
          "Kontrak & Termin"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bid_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "signed_at": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                    "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                  },
                  "termins": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "sequence": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 9007199254740991
                        },
                        "amount": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                        },
                        "due_date": {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        }
                      },
                      "required": [
                        "sequence",
                        "amount"
                      ]
                    }
                  }
                },
                "required": [
                  "bid_id",
                  "termins"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Kontrak dan termin dibuat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "provider": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string",
                          "description": "Nama penyedia demo (fiktif)"
                        },
                        "integrity_score": {
                          "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ],
                      "additionalProperties": false
                    },
                    "value": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "signed_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "termins": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "sequence": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 9007199254740991
                          },
                          "amount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "due_date": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "PLANNED",
                              "CLAIMED",
                              "FROZEN",
                              "RELEASED",
                              "PAID"
                            ]
                          },
                          "claimed_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "claim_note": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "frozen_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "freeze_reason": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "released_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "sequence",
                          "amount",
                          "due_date",
                          "status",
                          "claimed_at",
                          "claim_note",
                          "frozen_at",
                          "freeze_reason",
                          "released_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "frozen": {
                      "type": "boolean",
                      "description": "true bila ada termin berstatus FROZEN"
                    }
                  },
                  "required": [
                    "id",
                    "package_id",
                    "provider",
                    "value",
                    "signed_at",
                    "termins",
                    "frozen"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Data valid secara bentuk tetapi melanggar aturan bisnis (`UNPROCESSABLE`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/termins/{id}/claim": {
      "post": {
        "operationId": "termins.claim",
        "summary": "PPK mengajukan pencairan termin",
        "description": "Mengubah status termin `PLANNED → CLAIMED` dengan catatan progres fisik.\n\nPencairan hanya simulasi — tidak ada transfer nyata. Auditor dapat menahan termin ini sebelum `RELEASED`.",
        "tags": [
          "Kontrak & Termin"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 2000
                  },
                  "progress_pct": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100,
                    "description": "Progres fisik yang diklaim"
                  }
                },
                "required": [
                  "note",
                  "progress_pct"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Termin diajukan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "provider": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string",
                          "description": "Nama penyedia demo (fiktif)"
                        },
                        "integrity_score": {
                          "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ],
                      "additionalProperties": false
                    },
                    "value": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "signed_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "termins": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "sequence": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 9007199254740991
                          },
                          "amount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "due_date": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "PLANNED",
                              "CLAIMED",
                              "FROZEN",
                              "RELEASED",
                              "PAID"
                            ]
                          },
                          "claimed_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "claim_note": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "frozen_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "freeze_reason": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "released_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "sequence",
                          "amount",
                          "due_date",
                          "status",
                          "claimed_at",
                          "claim_note",
                          "frozen_at",
                          "freeze_reason",
                          "released_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "frozen": {
                      "type": "boolean",
                      "description": "true bila ada termin berstatus FROZEN"
                    }
                  },
                  "required": [
                    "id",
                    "package_id",
                    "provider",
                    "value",
                    "signed_at",
                    "termins",
                    "frozen"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/clarifications": {
      "post": {
        "operationId": "packages.clarify",
        "summary": "PPK menanggapi temuan auditor",
        "description": "Menjawab temuan/penahanan termin dengan penjelasan tertulis. Tanggapan tampil pada halaman investigasi Auditor dan tercatat di audit log.\n\nBody: `{ \"message\": string, \"termin_id\"?: uuid }`. Tidak mengubah status termin; Auditor yang memutuskan mencabut penahanan.",
        "tags": [
          "Kontrak & Termin",
          "Auditor"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PPK",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 2000
                  },
                  "termin_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  }
                },
                "required": [
                  "message"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Tanggapan tercatat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                    }
                  },
                  "required": [
                    "id",
                    "created_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/announce": {
      "post": {
        "operationId": "packages.announce",
        "summary": "Pokja mengumumkan paket",
        "description": "Transisi `DRAFT/DIUMUMKAN → PENAWARAN`. Paket muncul di peta publik dengan jadwal penawaran.\n\nSyarat: HPS sudah terkunci, `bid_close_at` di masa depan, dan minimal satu item HPS.\n\nSetelah pengumuman, HPS dan daftar item tidak dapat diubah (`409 STATE_CONFLICT`).",
        "tags": [
          "Pokja — Evaluasi & Penetapan"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "POKJA",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paket diumumkan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "code": {
                      "type": "string",
                      "description": "Kode paket/RUP demo, mis. DEMO-RUP-DRN-01"
                    },
                    "title": {
                      "type": "string"
                    },
                    "project_type": {
                      "type": "string",
                      "enum": [
                        "JALAN",
                        "DRAINASE",
                        "GEDUNG"
                      ]
                    },
                    "department_name": {
                      "type": "string"
                    },
                    "location": {
                      "type": "object",
                      "properties": {
                        "lat": {
                          "type": "number",
                          "minimum": -90,
                          "maximum": 90
                        },
                        "lng": {
                          "type": "number",
                          "minimum": -180,
                          "maximum": 180
                        }
                      },
                      "required": [
                        "lat",
                        "lng"
                      ],
                      "additionalProperties": false,
                      "description": "Koordinat WGS84"
                    },
                    "address": {
                      "type": "string"
                    },
                    "pagu": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Pagu anggaran paket"
                    },
                    "hps_total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Total Harga Perkiraan Sendiri"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "DRAFT",
                        "DIUMUMKAN",
                        "PENAWARAN",
                        "EVALUASI",
                        "PENETAPAN",
                        "SANGGAH",
                        "KONTRAK",
                        "PELAKSANAAN",
                        "SELESAI",
                        "DIBATALKAN"
                      ]
                    },
                    "risk_score": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    },
                    "risk_level": {
                      "type": "string",
                      "enum": [
                        "LOW",
                        "MEDIUM",
                        "HIGH"
                      ]
                    },
                    "bid_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Jumlah penawaran; tidak menampilkan harga sebelum penetapan"
                    },
                    "citizen_report_count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "is_demo": {
                      "type": "boolean"
                    },
                    "announced_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "bid_close_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "description": {
                      "type": "string"
                    },
                    "ppk_name": {
                      "type": "string"
                    },
                    "pokja_name": {
                      "type": "string"
                    },
                    "ai_summary": {
                      "description": "Ringkasan bahasa awam; null bila belum dibuat",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "flags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "INFO",
                              "WARNING",
                              "CRITICAL"
                            ]
                          },
                          "message": {
                            "type": "string",
                            "description": "Penjelasan bahasa awam"
                          },
                          "subject": {
                            "description": "Item/entitas yang ditandai",
                            "type": "string"
                          },
                          "value": {
                            "description": "Angka pendukung, mis. persentase di atas acuan",
                            "type": "number"
                          }
                        },
                        "required": [
                          "code",
                          "severity",
                          "message"
                        ],
                        "additionalProperties": false,
                        "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                      },
                      "description": "Indikator tingkat paket (HPS, penawar tunggal, rasio penawaran, dll.)"
                    },
                    "schedule": {
                      "type": "object",
                      "properties": {
                        "announcement": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_open": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "bid_close": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "evaluation": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "award": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "appeal_end": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "contract": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "announcement",
                        "bid_open",
                        "bid_close",
                        "evaluation",
                        "award",
                        "appeal_end",
                        "contract"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "id",
                    "code",
                    "title",
                    "project_type",
                    "department_name",
                    "location",
                    "pagu",
                    "hps_total",
                    "status",
                    "risk_score",
                    "risk_level",
                    "bid_count",
                    "citizen_report_count",
                    "is_demo",
                    "announced_at",
                    "bid_close_at",
                    "created_at",
                    "ai_summary",
                    "flags",
                    "schedule"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/bids/open": {
      "post": {
        "operationId": "packages.openBids",
        "summary": "Pokja membuka penawaran tersegel",
        "description": "Hanya boleh setelah `bid_close_at` terlewati; sebelum itu `409 STATE_CONFLICT`.\n\nHarga dan identitas penawar **tidak dibuka** sebelum `POST /api/packages/{id}/bids/open`. Yang tampil ke publik hanya hash komitmen.\n\nSetelah dibuka, setiap penawaran diverifikasi terhadap `commit_hash`-nya. Penawaran yang hash-nya tidak cocok ditandai `HASH_MISMATCH` dan tidak dapat ditetapkan sebagai pemenang.\n\nAksi tercatat di `audit_logs` (append-only, hash-chain) dengan `actor_role` pemanggil.",
        "tags": [
          "Pokja — Evaluasi & Penetapan",
          "Penyedia — Penawaran"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "POKJA",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Penawaran dibuka",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "opened_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                    },
                    "verified": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "mismatched": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "alias": {
                            "type": "string"
                          },
                          "expected": {
                            "type": "string"
                          },
                          "actual": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "alias",
                          "expected",
                          "actual"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "opened_at",
                    "verified",
                    "mismatched"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/evaluate": {
      "post": {
        "operationId": "packages.evaluate",
        "summary": "Pokja menjalankan evaluasi otomatis",
        "description": "Menghitung untuk setiap penawaran: flag harga per item vs HSPK, sinkron total, rasio terhadap HPS, kemiripan antar-penawar, dan penawar tunggal.\n\nPeringkat memakai **harga terkoreksi terendah di antara penawaran yang lulus**. Semua angka dihitung kode deterministik (`src/domain/`), bukan LLM.\n\nHasil tersimpan dan dapat dijalankan ulang; versi sebelumnya tetap di audit log.",
        "tags": [
          "Pokja — Evaluasi & Penetapan"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "POKJA",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Laporan evaluasi",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "evaluated_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "bid_count": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "single_bidder": {
                      "type": "boolean"
                    },
                    "hps_total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "entries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bid_id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "alias": {
                            "type": "string"
                          },
                          "provider": {
                            "description": "Terlihat oleh Pokja/Auditor; publik hanya setelah penetapan",
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "name": {
                                "type": "string",
                                "description": "Nama penyedia demo (fiktif)"
                              },
                              "integrity_score": {
                                "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 100
                              }
                            },
                            "required": [
                              "id",
                              "name"
                            ],
                            "additionalProperties": false
                          },
                          "total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "corrected_total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Total setelah koreksi aritmetika"
                          },
                          "hps_ratio_pct": {
                            "type": "number",
                            "description": "Total penawaran ÷ HPS × 100"
                          },
                          "total_synced": {
                            "type": "boolean"
                          },
                          "passed": {
                            "type": "boolean"
                          },
                          "rank": {
                            "anyOf": [
                              {
                                "type": "integer",
                                "minimum": -9007199254740991,
                                "maximum": 9007199254740991
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Peringkat harga terkoreksi terendah di antara yang lulus"
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string",
                                  "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "INFO",
                                    "WARNING",
                                    "CRITICAL"
                                  ]
                                },
                                "message": {
                                  "type": "string",
                                  "description": "Penjelasan bahasa awam"
                                },
                                "subject": {
                                  "description": "Item/entitas yang ditandai",
                                  "type": "string"
                                },
                                "value": {
                                  "description": "Angka pendukung, mis. persentase di atas acuan",
                                  "type": "number"
                                }
                              },
                              "required": [
                                "code",
                                "severity",
                                "message"
                              ],
                              "additionalProperties": false,
                              "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                            }
                          },
                          "similarity": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "peer_alias": {
                                  "type": "string"
                                },
                                "identical_item_pct": {
                                  "type": "number",
                                  "description": "Persentase item dengan harga satuan identik/hampir identik (±0,5%)"
                                },
                                "price_order_correlation": {
                                  "type": "number",
                                  "minimum": -1,
                                  "maximum": 1,
                                  "description": "Korelasi urutan harga antar-penawar"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "LOW",
                                    "MEDIUM",
                                    "HIGH"
                                  ]
                                }
                              },
                              "required": [
                                "peer_alias",
                                "identical_item_pct",
                                "price_order_correlation",
                                "severity"
                              ],
                              "additionalProperties": false
                            },
                            "description": "Kemiripan dengan penawar lain"
                          }
                        },
                        "required": [
                          "bid_id",
                          "alias",
                          "total",
                          "corrected_total",
                          "hps_ratio_pct",
                          "total_synced",
                          "passed",
                          "rank",
                          "flags",
                          "similarity"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Diurutkan berdasarkan peringkat"
                    }
                  },
                  "required": [
                    "package_id",
                    "evaluated_at",
                    "bid_count",
                    "single_bidder",
                    "hps_total",
                    "entries"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/evaluation": {
      "get": {
        "operationId": "packages.evaluation",
        "summary": "Baca laporan evaluasi",
        "description": "Laporan evaluasi terakhir. Identitas penyedia hanya untuk Pokja/Auditor; publik memakai `packages.results` setelah penetapan.",
        "tags": [
          "Pokja — Evaluasi & Penetapan",
          "Auditor"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "POKJA",
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Laporan evaluasi",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "evaluated_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "bid_count": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "single_bidder": {
                      "type": "boolean"
                    },
                    "hps_total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "entries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bid_id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "alias": {
                            "type": "string"
                          },
                          "provider": {
                            "description": "Terlihat oleh Pokja/Auditor; publik hanya setelah penetapan",
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "name": {
                                "type": "string",
                                "description": "Nama penyedia demo (fiktif)"
                              },
                              "integrity_score": {
                                "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 100
                              }
                            },
                            "required": [
                              "id",
                              "name"
                            ],
                            "additionalProperties": false
                          },
                          "total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "corrected_total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Total setelah koreksi aritmetika"
                          },
                          "hps_ratio_pct": {
                            "type": "number",
                            "description": "Total penawaran ÷ HPS × 100"
                          },
                          "total_synced": {
                            "type": "boolean"
                          },
                          "passed": {
                            "type": "boolean"
                          },
                          "rank": {
                            "anyOf": [
                              {
                                "type": "integer",
                                "minimum": -9007199254740991,
                                "maximum": 9007199254740991
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Peringkat harga terkoreksi terendah di antara yang lulus"
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string",
                                  "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "INFO",
                                    "WARNING",
                                    "CRITICAL"
                                  ]
                                },
                                "message": {
                                  "type": "string",
                                  "description": "Penjelasan bahasa awam"
                                },
                                "subject": {
                                  "description": "Item/entitas yang ditandai",
                                  "type": "string"
                                },
                                "value": {
                                  "description": "Angka pendukung, mis. persentase di atas acuan",
                                  "type": "number"
                                }
                              },
                              "required": [
                                "code",
                                "severity",
                                "message"
                              ],
                              "additionalProperties": false,
                              "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                            }
                          },
                          "similarity": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "peer_alias": {
                                  "type": "string"
                                },
                                "identical_item_pct": {
                                  "type": "number",
                                  "description": "Persentase item dengan harga satuan identik/hampir identik (±0,5%)"
                                },
                                "price_order_correlation": {
                                  "type": "number",
                                  "minimum": -1,
                                  "maximum": 1,
                                  "description": "Korelasi urutan harga antar-penawar"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "LOW",
                                    "MEDIUM",
                                    "HIGH"
                                  ]
                                }
                              },
                              "required": [
                                "peer_alias",
                                "identical_item_pct",
                                "price_order_correlation",
                                "severity"
                              ],
                              "additionalProperties": false
                            },
                            "description": "Kemiripan dengan penawar lain"
                          }
                        },
                        "required": [
                          "bid_id",
                          "alias",
                          "total",
                          "corrected_total",
                          "hps_ratio_pct",
                          "total_synced",
                          "passed",
                          "rank",
                          "flags",
                          "similarity"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Diurutkan berdasarkan peringkat"
                    }
                  },
                  "required": [
                    "package_id",
                    "evaluated_at",
                    "bid_count",
                    "single_bidder",
                    "hps_total",
                    "entries"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/award": {
      "post": {
        "operationId": "packages.award",
        "summary": "Pokja menetapkan pemenang",
        "description": "Transisi `EVALUASI → PENETAPAN`. Hanya penawaran yang lulus yang dapat ditetapkan.\n\nBila `bid_id` bukan peringkat 1, `not_lowest_reason` wajib (minimal 20 karakter) dan ikut dipublikasikan.\n\nRincian penawaran pemenang dibuka ke publik setelah masa sanggah berakhir (`packages.results`).",
        "tags": [
          "Pokja — Evaluasi & Penetapan"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "POKJA",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bid_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "not_lowest_reason": {
                    "description": "Wajib bila bid_id bukan peringkat 1",
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 2000
                  }
                },
                "required": [
                  "bid_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Penetapan tercatat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "bid_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "provider": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string",
                          "description": "Nama penyedia demo (fiktif)"
                        },
                        "integrity_score": {
                          "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ],
                      "additionalProperties": false
                    },
                    "value": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nilai penawaran pemenang (menjadi nilai kontrak)"
                    },
                    "decided_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "rank": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "not_lowest_reason": {
                      "description": "Wajib bila pemenang bukan peringkat 1",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "appeal_end_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    }
                  },
                  "required": [
                    "package_id",
                    "bid_id",
                    "provider",
                    "value",
                    "decided_at",
                    "rank",
                    "not_lowest_reason",
                    "appeal_end_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Data valid secara bentuk tetapi melanggar aturan bisnis (`UNPROCESSABLE`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/appeals": {
      "get": {
        "operationId": "appeals.list",
        "summary": "Daftar sanggah paket",
        "description": "Sanggah dari penyedia beserta jawaban Pokja. Publik hanya melihat sanggah yang sudah dijawab setelah masa sanggah berakhir.",
        "tags": [
          "Pokja — Evaluasi & Penetapan"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "POKJA",
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daftar sanggah",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                      },
                      "package_id": {
                        "type": "string",
                        "format": "uuid",
                        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                      },
                      "bid_id": {
                        "type": "string",
                        "format": "uuid",
                        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                      },
                      "reason": {
                        "type": "string"
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "OPEN",
                          "ANSWERED",
                          "REJECTED"
                        ]
                      },
                      "submitted_at": {
                        "type": "string",
                        "format": "date-time",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                        "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                      },
                      "response": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "responded_at": {
                        "anyOf": [
                          {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      }
                    },
                    "required": [
                      "id",
                      "package_id",
                      "bid_id",
                      "reason",
                      "status",
                      "submitted_at",
                      "response",
                      "responded_at"
                    ],
                    "additionalProperties": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "appeals.create",
        "summary": "Penyedia mengajukan sanggah",
        "description": "Diajukan selama masa sanggah setelah penetapan. Wajib menyebutkan alasan; sistem menolak sanggah di luar jendela waktu (`409 STATE_CONFLICT`).\n\nSatu penyedia maksimal satu sanggah per paket. Sanggah tidak otomatis menunda kontrak; Pokja menjawab lewat `appeals.respond`.",
        "tags": [
          "Penyedia — Penawaran"
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PENYEDIA"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 30,
                    "maxLength": 4000
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sanggah tercatat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "bid_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "reason": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "OPEN",
                        "ANSWERED",
                        "REJECTED"
                      ]
                    },
                    "submitted_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "response": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "responded_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "package_id",
                    "bid_id",
                    "reason",
                    "status",
                    "submitted_at",
                    "response",
                    "responded_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/appeals/{id}/respond": {
      "post": {
        "operationId": "appeals.respond",
        "summary": "Pokja menjawab sanggah",
        "description": "Menjawab sanggah dengan status `ANSWERED` atau `REJECTED`. Wajib alasan tertulis; keputusan tercatat di audit log.\n\nBila sanggah diterima dan memengaruhi peringkat, Pokja harus menjalankan evaluasi ulang sebelum menetapkan pemenang baru.",
        "tags": [
          "Pokja — Evaluasi & Penetapan"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "POKJA",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "response": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 4000
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "ANSWERED",
                      "REJECTED"
                    ]
                  }
                },
                "required": [
                  "response",
                  "status"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sanggah dijawab",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "bid_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "reason": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "OPEN",
                        "ANSWERED",
                        "REJECTED"
                      ]
                    },
                    "submitted_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "response": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "responded_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "package_id",
                    "bid_id",
                    "reason",
                    "status",
                    "submitted_at",
                    "response",
                    "responded_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/my-bid": {
      "get": {
        "operationId": "bids.mine",
        "summary": "Penawaran milik penyedia yang masuk",
        "description": "Mengembalikan penawaran milik sesi ini pada paket tersebut, termasuk draf dan riwayat revisi.\nPenyedia lain tidak dapat membaca endpoint ini; `403` bila bukan pemilik.",
        "tags": [
          "Penyedia — Penawaran"
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PENYEDIA"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Penawaran milik sendiri",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "package_id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "DRAFT",
                            "SUBMITTED",
                            "OPENED",
                            "EVALUATED",
                            "AWARDED",
                            "REJECTED",
                            "WITHDRAWN"
                          ]
                        },
                        "total": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "item_name": {
                                "type": "string"
                              },
                              "unit": {
                                "type": "string"
                              },
                              "volume": {
                                "type": "number",
                                "exclusiveMinimum": 0
                              },
                              "unit_price": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                              },
                              "amount": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                              },
                              "ssh_code": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "ssh_price": {
                                "anyOf": [
                                  {
                                    "type": "integer",
                                    "minimum": 0,
                                    "maximum": 9007199254740991,
                                    "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ]
                              },
                              "markup_pct": {
                                "description": "Selisih harga penawaran terhadap HSPK",
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "flags": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "code": {
                                      "type": "string",
                                      "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                    },
                                    "severity": {
                                      "type": "string",
                                      "enum": [
                                        "INFO",
                                        "WARNING",
                                        "CRITICAL"
                                      ]
                                    },
                                    "message": {
                                      "type": "string",
                                      "description": "Penjelasan bahasa awam"
                                    },
                                    "subject": {
                                      "description": "Item/entitas yang ditandai",
                                      "type": "string"
                                    },
                                    "value": {
                                      "description": "Angka pendukung, mis. persentase di atas acuan",
                                      "type": "number"
                                    }
                                  },
                                  "required": [
                                    "code",
                                    "severity",
                                    "message"
                                  ],
                                  "additionalProperties": false,
                                  "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                                }
                              }
                            },
                            "required": [
                              "id",
                              "item_name",
                              "unit",
                              "volume",
                              "unit_price",
                              "amount",
                              "ssh_code",
                              "ssh_price",
                              "markup_pct",
                              "flags"
                            ],
                            "additionalProperties": false
                          }
                        },
                        "commit_hash": {
                          "anyOf": [
                            {
                              "type": "string",
                              "pattern": "^[0-9a-f]{64}$",
                              "description": "SHA-256 heksadesimal 64 karakter"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Diisi saat penawaran dikirim (sealed)"
                        },
                        "submitted_at": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "opened_at": {
                          "anyOf": [
                            {
                              "type": "string",
                              "format": "date-time",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                              "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "can_revise": {
                          "type": "boolean",
                          "description": "true hanya selama status SUBMITTED dan sebelum bid_close_at"
                        },
                        "revision": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 9007199254740991,
                          "description": "Nomor revisi; versi lama tetap tersimpan"
                        }
                      },
                      "required": [
                        "id",
                        "package_id",
                        "status",
                        "total",
                        "items",
                        "commit_hash",
                        "submitted_at",
                        "opened_at",
                        "can_revise",
                        "revision"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "bids.save",
        "summary": "Simpan draf penawaran",
        "description": "Membuat atau mengganti draf penawaran (item, volume, harga satuan). Total dihitung server.\n\nDiizinkan selama paket berstatus `PENAWARAN` dan `bid_close_at` belum lewat. Setelah dikirim (`submit`), revisi tetap boleh sampai penutupan, tetapi versi lama tetap tersimpan dan nomor revisi bertambah.\n\nValidasi: minimal satu item; volume > 0; harga satuan ≥ 0; `ssh_code` bila ada harus dikenal.",
        "tags": [
          "Penyedia — Penawaran"
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PENYEDIA"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "ssh_code": {
                          "type": "string"
                        },
                        "item_name": {
                          "type": "string",
                          "minLength": 2,
                          "maxLength": 300
                        },
                        "unit": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 20
                        },
                        "volume": {
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "unit_price": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                        }
                      },
                      "required": [
                        "volume",
                        "unit_price"
                      ]
                    }
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "items"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draf tersimpan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "DRAFT",
                        "SUBMITTED",
                        "OPENED",
                        "EVALUATED",
                        "AWARDED",
                        "REJECTED",
                        "WITHDRAWN"
                      ]
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "item_name": {
                            "type": "string"
                          },
                          "unit": {
                            "type": "string"
                          },
                          "volume": {
                            "type": "number",
                            "exclusiveMinimum": 0
                          },
                          "unit_price": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "amount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "ssh_code": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "ssh_price": {
                            "anyOf": [
                              {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "markup_pct": {
                            "description": "Selisih harga penawaran terhadap HSPK",
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string",
                                  "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "INFO",
                                    "WARNING",
                                    "CRITICAL"
                                  ]
                                },
                                "message": {
                                  "type": "string",
                                  "description": "Penjelasan bahasa awam"
                                },
                                "subject": {
                                  "description": "Item/entitas yang ditandai",
                                  "type": "string"
                                },
                                "value": {
                                  "description": "Angka pendukung, mis. persentase di atas acuan",
                                  "type": "number"
                                }
                              },
                              "required": [
                                "code",
                                "severity",
                                "message"
                              ],
                              "additionalProperties": false,
                              "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                            }
                          }
                        },
                        "required": [
                          "id",
                          "item_name",
                          "unit",
                          "volume",
                          "unit_price",
                          "amount",
                          "ssh_code",
                          "ssh_price",
                          "markup_pct",
                          "flags"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "commit_hash": {
                      "anyOf": [
                        {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "SHA-256 heksadesimal 64 karakter"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Diisi saat penawaran dikirim (sealed)"
                    },
                    "submitted_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "opened_at": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                          "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "can_revise": {
                      "type": "boolean",
                      "description": "true hanya selama status SUBMITTED dan sebelum bid_close_at"
                    },
                    "revision": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 9007199254740991,
                      "description": "Nomor revisi; versi lama tetap tersimpan"
                    }
                  },
                  "required": [
                    "id",
                    "package_id",
                    "status",
                    "total",
                    "items",
                    "commit_hash",
                    "submitted_at",
                    "opened_at",
                    "can_revise",
                    "revision"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/my-bid/submit": {
      "post": {
        "operationId": "bids.submit",
        "summary": "Kirim penawaran tersegel (komitmen hash)",
        "description": "Menghitung `commit_hash = SHA-256(kanonik isi penawaran)` lalu menandai penawaran `SUBMITTED`.\n\nHash ditampilkan ke penyedia dan dipublikasikan di halaman paket (`packages.commitments`), sehingga isi penawaran tidak dapat diubah setelah penutupan tanpa ketahuan.\n\nPengiriman bersifat idempoten untuk isi yang sama: mengirim ulang draf yang identik mengembalikan hash yang sama.\n\nHarga dan identitas penawar **tidak dibuka** sebelum `POST /api/packages/{id}/bids/open`. Yang tampil ke publik hanya hash komitmen.\n\nAksi tercatat di `audit_logs` (append-only, hash-chain) dengan `actor_role` pemanggil.",
        "tags": [
          "Penyedia — Penawaran"
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PENYEDIA"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bukti komitmen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bid_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "commit_hash": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$",
                      "description": "SHA-256 atas isi penawaran; dipublikasikan di halaman paket"
                    },
                    "submitted_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "revision": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    }
                  },
                  "required": [
                    "bid_id",
                    "commit_hash",
                    "submitted_at",
                    "revision",
                    "total"
                  ],
                  "additionalProperties": false,
                  "description": "Bukti komitmen: hash dapat diverifikasi ulang terhadap isi penawaran yang dibuka"
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/packages/{id}/my-bid/import": {
      "post": {
        "operationId": "bids.import",
        "summary": "Impor PDF RAB menjadi draf item (opsional)",
        "description": "Mengekstrak tabel RAB dari PDF memakai Gemini, lalu mengembalikan **draf** item untuk dikonfirmasi penyedia. Tidak langsung menyimpan penawaran.\n\nHasil di-cache per SHA-256 berkas. LLM hanya mengekstrak dan memilih `ssh_code`; seluruh perhitungan dilakukan kode.\n\nBatas: PDF maks. 10 MB, magic bytes `%PDF-`, 5 unggahan/menit per akun.",
        "tags": [
          "Penyedia — Penawaran"
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "PENYEDIA"
        ],
        "x-rate-limit": "5 unggahan/menit per akun",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draf hasil ekstraksi",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ssh_code": {
                            "type": "string"
                          },
                          "item_name": {
                            "type": "string",
                            "minLength": 2,
                            "maxLength": 300
                          },
                          "unit": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 20
                          },
                          "volume": {
                            "type": "number",
                            "exclusiveMinimum": 0
                          },
                          "unit_price": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          }
                        },
                        "required": [
                          "volume",
                          "unit_price"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Item yang tidak dapat dipadankan dengan HSPK"
                    },
                    "model": {
                      "type": "string"
                    },
                    "cached": {
                      "type": "boolean",
                      "description": "true bila hasil diambil dari cache sha256"
                    }
                  },
                  "required": [
                    "items",
                    "total",
                    "warnings",
                    "model",
                    "cached"
                  ],
                  "additionalProperties": false,
                  "description": "Hasil ekstraksi PDF sebagai draf; penyedia tetap wajib mengonfirmasi"
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Berkas melebihi batas (`PAYLOAD_TOO_LARGE`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Data valid secara bentuk tetapi melanggar aturan bisnis (`UNPROCESSABLE`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Layanan ekstraksi gagal (`UPSTREAM_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auditor/queue": {
      "get": {
        "operationId": "auditor.queue",
        "summary": "Antrean risiko Auditor",
        "description": "Paket diurutkan berdasarkan skor risiko gabungan (harga, kemiripan antar-penawar, laporan warga), beserta komponen skor agar dapat dijelaskan.\n\nHanya untuk Auditor/Admin. Skor adalah indikator prioritas pemeriksaan, bukan vonis.",
        "tags": [
          "Auditor"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "min_score",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100
            }
          },
          {
            "name": "only_frozen",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 20,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Antrean risiko",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "package_id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "title": {
                            "type": "string"
                          },
                          "department_name": {
                            "type": "string"
                          },
                          "risk_score": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 100
                          },
                          "components": {
                            "type": "object",
                            "properties": {
                              "price": {
                                "type": "number",
                                "description": "Kontribusi skor dari selisih harga"
                              },
                              "similarity": {
                                "type": "number",
                                "description": "Kontribusi dari kemiripan antar-penawar"
                              },
                              "reports": {
                                "type": "number",
                                "description": "Kontribusi dari laporan warga"
                              }
                            },
                            "required": [
                              "price",
                              "similarity",
                              "reports"
                            ],
                            "additionalProperties": false
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "code": {
                                  "type": "string",
                                  "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "INFO",
                                    "WARNING",
                                    "CRITICAL"
                                  ]
                                },
                                "message": {
                                  "type": "string",
                                  "description": "Penjelasan bahasa awam"
                                },
                                "subject": {
                                  "description": "Item/entitas yang ditandai",
                                  "type": "string"
                                },
                                "value": {
                                  "description": "Angka pendukung, mis. persentase di atas acuan",
                                  "type": "number"
                                }
                              },
                              "required": [
                                "code",
                                "severity",
                                "message"
                              ],
                              "additionalProperties": false,
                              "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                            }
                          },
                          "hps_total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "bid_count": {
                            "type": "integer",
                            "minimum": -9007199254740991,
                            "maximum": 9007199254740991
                          },
                          "frozen_termins": {
                            "type": "integer",
                            "minimum": -9007199254740991,
                            "maximum": 9007199254740991
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          }
                        },
                        "required": [
                          "package_id",
                          "title",
                          "department_name",
                          "risk_score",
                          "components",
                          "flags",
                          "hps_total",
                          "bid_count",
                          "frozen_termins",
                          "updated_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "next_cursor": {
                      "description": "Cursor halaman berikutnya; null bila sudah habis",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auditor/packages/{id}": {
      "get": {
        "operationId": "auditor.investigation",
        "summary": "Investigasi side-by-side",
        "description": "Satu layar berisi HPS, seluruh penawaran (harga per item vs HSPK), laporan warga, dan status termin.\n\nIdentitas penyedia terlihat oleh Auditor; tetap tidak boleh mengubah data paket atau penawaran.",
        "tags": [
          "Auditor"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tampilan investigasi",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string"
                        },
                        "hps_total": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991,
                          "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                        },
                        "department_name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "title",
                        "hps_total",
                        "department_name"
                      ],
                      "additionalProperties": false
                    },
                    "hps_items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "item_name": {
                            "type": "string"
                          },
                          "unit": {
                            "type": "string"
                          },
                          "volume": {
                            "type": "number"
                          },
                          "unit_price": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "ssh_price": {
                            "anyOf": [
                              {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "markup_pct": {
                            "type": [
                              "number",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "item_name",
                          "unit",
                          "volume",
                          "unit_price",
                          "ssh_price",
                          "markup_pct"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "bids": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "alias": {
                            "type": "string"
                          },
                          "total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "hps_ratio_pct": {
                            "type": "number"
                          },
                          "items": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "bid_item_id": {
                                  "type": "string",
                                  "format": "uuid",
                                  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                                },
                                "item_name": {
                                  "type": "string"
                                },
                                "unit_price": {
                                  "type": "integer",
                                  "minimum": 0,
                                  "maximum": 9007199254740991,
                                  "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                                },
                                "ssh_code": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "ssh_price": {
                                  "anyOf": [
                                    {
                                      "type": "integer",
                                      "minimum": 0,
                                      "maximum": 9007199254740991,
                                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ]
                                },
                                "markup_pct": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "verified": {
                                  "type": "boolean"
                                },
                                "verified_by": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "verified_at": {
                                  "anyOf": [
                                    {
                                      "type": "string",
                                      "format": "date-time",
                                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ]
                                },
                                "verification_source": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "flags": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "code": {
                                        "type": "string",
                                        "description": "Kode temuan stabil, mis. PRICE_ABOVE_HSPK"
                                      },
                                      "severity": {
                                        "type": "string",
                                        "enum": [
                                          "INFO",
                                          "WARNING",
                                          "CRITICAL"
                                        ]
                                      },
                                      "message": {
                                        "type": "string",
                                        "description": "Penjelasan bahasa awam"
                                      },
                                      "subject": {
                                        "description": "Item/entitas yang ditandai",
                                        "type": "string"
                                      },
                                      "value": {
                                        "description": "Angka pendukung, mis. persentase di atas acuan",
                                        "type": "number"
                                      }
                                    },
                                    "required": [
                                      "code",
                                      "severity",
                                      "message"
                                    ],
                                    "additionalProperties": false,
                                    "description": "Indikator untuk diklarifikasi — bukan kesimpulan pelanggaran"
                                  }
                                }
                              },
                              "required": [
                                "bid_item_id",
                                "item_name",
                                "unit_price",
                                "ssh_code",
                                "ssh_price",
                                "markup_pct",
                                "verified",
                                "verified_by",
                                "verified_at",
                                "verification_source",
                                "flags"
                              ],
                              "additionalProperties": false
                            }
                          }
                        },
                        "required": [
                          "alias",
                          "total",
                          "hps_ratio_pct",
                          "items"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Side-by-side; identitas penyedia hanya untuk Auditor"
                    },
                    "reports": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "package_id": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "null bila tidak ada paket dalam radius 50 m"
                          },
                          "report_type": {
                            "type": "string",
                            "enum": [
                              "PHYSICAL_ISSUE",
                              "BOARD_MISMATCH",
                              "STALLED_PROJECT"
                            ]
                          },
                          "description": {
                            "type": "string"
                          },
                          "photo_url": {
                            "type": "string",
                            "description": "URL publik; metadata EXIF sudah dihapus"
                          },
                          "location": {
                            "type": "object",
                            "properties": {
                              "lat": {
                                "type": "number",
                                "minimum": -90,
                                "maximum": 90
                              },
                              "lng": {
                                "type": "number",
                                "minimum": -180,
                                "maximum": 180
                              }
                            },
                            "required": [
                              "lat",
                              "lng"
                            ],
                            "additionalProperties": false,
                            "description": "Titik kejadian; dibulatkan 4 desimal pada respons publik"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "PENDING",
                              "VERIFIED",
                              "RESOLVED",
                              "REJECTED"
                            ]
                          },
                          "cluster_id": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Laporan ganda dalam 50 m digabung, aslinya tetap tersimpan"
                          },
                          "duplicate_of": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "vision": {
                            "anyOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "confidence": {
                                    "type": "number",
                                    "minimum": 0,
                                    "maximum": 1
                                  },
                                  "matches_category": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "label",
                                  "confidence",
                                  "matches_category"
                                ],
                                "additionalProperties": false
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Indikator bantu dari model visi, bukan penentu"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          }
                        },
                        "required": [
                          "id",
                          "package_id",
                          "report_type",
                          "description",
                          "photo_url",
                          "location",
                          "status",
                          "cluster_id",
                          "duplicate_of",
                          "vision",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "termins": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "sequence": {
                            "type": "number"
                          },
                          "amount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "status": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "sequence",
                          "amount",
                          "status"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "package_id",
                    "package",
                    "hps_items",
                    "bids",
                    "reports",
                    "termins"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/termins/{id}/freeze": {
      "post": {
        "operationId": "auditor.freeze",
        "summary": "Tahan pencairan termin",
        "description": "Mengubah status termin `CLAIMED → FROZEN`. Alasan wajib dan dipublikasikan pada detail paket sebagai “Termin Ditahan”.\n\nPPK dapat menanggapi lewat `packages.clarify`; Auditor yang memutuskan mencabut penahanan.\n\nAksi tercatat di `audit_logs` (append-only, hash-chain) dengan `actor_role` pemanggil.",
        "tags": [
          "Auditor",
          "Kontrak & Termin"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 2000,
                    "description": "Wajib; tercatat di audit log"
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Termin ditahan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "provider": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string",
                          "description": "Nama penyedia demo (fiktif)"
                        },
                        "integrity_score": {
                          "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ],
                      "additionalProperties": false
                    },
                    "value": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "signed_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "termins": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "sequence": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 9007199254740991
                          },
                          "amount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "due_date": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "PLANNED",
                              "CLAIMED",
                              "FROZEN",
                              "RELEASED",
                              "PAID"
                            ]
                          },
                          "claimed_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "claim_note": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "frozen_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "freeze_reason": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "released_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "sequence",
                          "amount",
                          "due_date",
                          "status",
                          "claimed_at",
                          "claim_note",
                          "frozen_at",
                          "freeze_reason",
                          "released_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "frozen": {
                      "type": "boolean",
                      "description": "true bila ada termin berstatus FROZEN"
                    }
                  },
                  "required": [
                    "id",
                    "package_id",
                    "provider",
                    "value",
                    "signed_at",
                    "termins",
                    "frozen"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/termins/{id}/release": {
      "post": {
        "operationId": "auditor.release",
        "summary": "Cabut penahanan termin",
        "description": "Mengubah status termin `FROZEN → RELEASED` setelah klarifikasi dinilai memadai. Wajib catatan.\n\nAksi tercatat di `audit_logs` (append-only, hash-chain) dengan `actor_role` pemanggil.",
        "tags": [
          "Auditor",
          "Kontrak & Termin"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 2000
                  }
                },
                "required": [
                  "note"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Penahanan dicabut",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package_id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "provider": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string",
                          "description": "Nama penyedia demo (fiktif)"
                        },
                        "integrity_score": {
                          "description": "Skor Integritas Penyedia; hanya untuk penyedia demo",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ],
                      "additionalProperties": false
                    },
                    "value": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                    },
                    "signed_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    },
                    "termins": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "sequence": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 9007199254740991
                          },
                          "amount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Nominal rupiah bulat, tanpa pecahan. Contoh: 190342598"
                          },
                          "due_date": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "PLANNED",
                              "CLAIMED",
                              "FROZEN",
                              "RELEASED",
                              "PAID"
                            ]
                          },
                          "claimed_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "claim_note": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "frozen_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "freeze_reason": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "released_at": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                                "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "sequence",
                          "amount",
                          "due_date",
                          "status",
                          "claimed_at",
                          "claim_note",
                          "frozen_at",
                          "freeze_reason",
                          "released_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "frozen": {
                      "type": "boolean",
                      "description": "true bila ada termin berstatus FROZEN"
                    }
                  },
                  "required": [
                    "id",
                    "package_id",
                    "provider",
                    "value",
                    "signed_at",
                    "termins",
                    "frozen"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auditor/items/{id}/verify": {
      "post": {
        "operationId": "auditor.verifyItem",
        "summary": "Tandai item terverifikasi dokumen resmi",
        "description": "Menandai satu item penawaran sebagai sudah dicocokkan dengan dokumen resmi (mis. kontrak/SPK), beserta sumbernya.\n\nTanda ini tampil di halaman investigasi; tidak mengubah harga maupun peringkat.",
        "tags": [
          "Auditor"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "source": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 300,
                    "description": "Rujukan dokumen resmi yang dipakai memverifikasi"
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 1000
                  }
                },
                "required": [
                  "source"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Item terverifikasi",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "const": true
                    },
                    "verified_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                    }
                  },
                  "required": [
                    "verified",
                    "verified_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Belum terautentikasi (`UNAUTHORIZED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Peran tidak diizinkan (`FORBIDDEN`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Bertentangan dengan state saat ini (`CONFLICT` / `STATE_CONFLICT`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/reports": {
      "get": {
        "operationId": "auditor.reports",
        "summary": "Antrean laporan warga",
        "description": "Laporan warga terbaru, dengan filter status dan paket. Hanya Auditor/Admin yang melihat antrean penuh.",
        "tags": [
          "Auditor",
          "Laporan Warga"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "package_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "PENDING",
                "VERIFIED",
                "RESOLVED",
                "REJECTED"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 20,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daftar laporan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "package_id": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "null bila tidak ada paket dalam radius 50 m"
                          },
                          "report_type": {
                            "type": "string",
                            "enum": [
                              "PHYSICAL_ISSUE",
                              "BOARD_MISMATCH",
                              "STALLED_PROJECT"
                            ]
                          },
                          "description": {
                            "type": "string"
                          },
                          "photo_url": {
                            "type": "string",
                            "description": "URL publik; metadata EXIF sudah dihapus"
                          },
                          "location": {
                            "type": "object",
                            "properties": {
                              "lat": {
                                "type": "number",
                                "minimum": -90,
                                "maximum": 90
                              },
                              "lng": {
                                "type": "number",
                                "minimum": -180,
                                "maximum": 180
                              }
                            },
                            "required": [
                              "lat",
                              "lng"
                            ],
                            "additionalProperties": false,
                            "description": "Titik kejadian; dibulatkan 4 desimal pada respons publik"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "PENDING",
                              "VERIFIED",
                              "RESOLVED",
                              "REJECTED"
                            ]
                          },
                          "cluster_id": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Laporan ganda dalam 50 m digabung, aslinya tetap tersimpan"
                          },
                          "duplicate_of": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "vision": {
                            "anyOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "confidence": {
                                    "type": "number",
                                    "minimum": 0,
                                    "maximum": 1
                                  },
                                  "matches_category": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "label",
                                  "confidence",
                                  "matches_category"
                                ],
                                "additionalProperties": false
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Indikator bantu dari model visi, bukan penentu"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                            "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                          }
                        },
                        "required": [
                          "id",
                          "package_id",
                          "report_type",
                          "description",
                          "photo_url",
                          "location",
                          "status",
                          "cluster_id",
                          "duplicate_of",
                          "vision",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "next_cursor": {
                      "description": "Cursor halaman berikutnya; null bila sudah habis",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "reports.create",
        "summary": "Warga mengirim laporan lapangan",
        "description": "Terbuka tanpa akun. Body `multipart/form-data`: foto (JPEG/PNG/WebP, maks. 8 MB), jenis laporan, deskripsi, dan koordinat.\n\nServer melakukan: validasi magic bytes (bukan hanya MIME), **hapus EXIF** dari versi publik, cari paket dalam radius 50 m (`ST_DWithin`), gabungkan laporan ganda menjadi cluster tanpa menghapus laporan asli, dan menjalankan pemeriksaan visi Gemini sebagai indikator bantu.\n\nPrivasi: respons publik hanya memuat koordinat yang dibulatkan 4 desimal dan foto tanpa EXIF. Tidak ada identitas pelapor.\n\nRate limit: 5 laporan/10 menit per IP.",
        "tags": [
          "Laporan Warga"
        ],
        "security": [],
        "x-status": "planned",
        "x-roles": [
          "PUBLIK"
        ],
        "x-rate-limit": "5 laporan/10 menit per IP",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "package_id": {
                    "description": "Bila kosong, sistem mencari paket terdekat dalam 50 m",
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "report_type": {
                    "type": "string",
                    "enum": [
                      "PHYSICAL_ISSUE",
                      "BOARD_MISMATCH",
                      "STALLED_PROJECT"
                    ]
                  },
                  "description": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 2000
                  },
                  "lat": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                  },
                  "lng": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                  },
                  "photo": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "report_type",
                  "description",
                  "lat",
                  "lng",
                  "photo"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Laporan diterima",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "package_id": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "null bila tidak ada paket dalam radius 50 m"
                    },
                    "report_type": {
                      "type": "string",
                      "enum": [
                        "PHYSICAL_ISSUE",
                        "BOARD_MISMATCH",
                        "STALLED_PROJECT"
                      ]
                    },
                    "description": {
                      "type": "string"
                    },
                    "photo_url": {
                      "type": "string",
                      "description": "URL publik; metadata EXIF sudah dihapus"
                    },
                    "location": {
                      "type": "object",
                      "properties": {
                        "lat": {
                          "type": "number",
                          "minimum": -90,
                          "maximum": 90
                        },
                        "lng": {
                          "type": "number",
                          "minimum": -180,
                          "maximum": 180
                        }
                      },
                      "required": [
                        "lat",
                        "lng"
                      ],
                      "additionalProperties": false,
                      "description": "Titik kejadian; dibulatkan 4 desimal pada respons publik"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "PENDING",
                        "VERIFIED",
                        "RESOLVED",
                        "REJECTED"
                      ]
                    },
                    "cluster_id": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Laporan ganda dalam 50 m digabung, aslinya tetap tersimpan"
                    },
                    "duplicate_of": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "vision": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "label": {
                              "type": "string"
                            },
                            "confidence": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "matches_category": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "label",
                            "confidence",
                            "matches_category"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Indikator bantu dari model visi, bukan penentu"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                      "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                    }
                  },
                  "required": [
                    "id",
                    "package_id",
                    "report_type",
                    "description",
                    "photo_url",
                    "location",
                    "status",
                    "cluster_id",
                    "duplicate_of",
                    "vision",
                    "created_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Berkas melebihi batas (`PAYLOAD_TOO_LARGE`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Data valid secara bentuk tetapi melanggar aturan bisnis (`UNPROCESSABLE`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Layanan ekstraksi gagal (`UPSTREAM_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auditor/packages/{id}/log": {
      "get": {
        "operationId": "auditor.actionLog",
        "summary": "Jejak audit satu paket",
        "description": "Seluruh aksi pada paket dan entitas turunannya, terurut terbaru.",
        "tags": [
          "Auditor",
          "Sistem"
        ],
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "x-status": "planned",
        "x-roles": [
          "AUDITOR",
          "ADMIN"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entri audit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "action": {
                        "type": "string"
                      },
                      "actor_role": {
                        "type": "string",
                        "enum": [
                          "PUBLIK",
                          "PENYEDIA",
                          "POKJA",
                          "PPK",
                          "AUDITOR",
                          "ADMIN"
                        ]
                      },
                      "actor_name": {
                        "type": "string"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
                        "description": "Waktu ISO-8601 dengan zona, mis. 2026-11-15T08:30:00+07:00"
                      },
                      "previous_state": {
                        "type": "string",
                        "format": "binary"
                      },
                      "new_state": {
                        "type": "string",
                        "format": "binary"
                      }
                    },
                    "required": [
                      "id",
                      "action",
                      "actor_role",
                      "created_at"
                    ],
                    "additionalProperties": false,
                    "description": "Satu baris audit log ber-hash-chain"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Permintaan tidak valid (`VALIDATION_ERROR`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entitas tidak ditemukan (`NOT_FOUND`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Melebihi batas laju (`RATE_LIMITED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Galat tak terduga (`INTERNAL`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}