[MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY] Sinkronisasi On-Demand Kapal Inaportnet, Optimasi Evaluasi Status Superset Summary, Refaktorisasi Summary Status Service V2, dan Mitigasi Keamanan Brace-Expansion
Modul: Pengangkut | Tipe: Refactoring | Stage Saat Ini:
STAGING| Tanggal Dokumen: 2026-09-04
1. Informasi Tugas & Referensi Bisnis
Task ID / Nomor Referensi:
MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITYSumber Instruksi: Git Branch
feat/update-kapal,feat/superset-summary-optimizations,refactor/post-status-helper,fix/security-override-brace-expansionDokumen Referensi / Lampiran: Tidak ada
Task / Dependensi Terkait (Cross-Service): REFACTOR-DEPRECATE-POST-STATUS-SERVICE-MIGRATION
Modul Terkait:
PengangkutService / Repository:
pengangkut-backendPIC Pengembang: Hermansyah Efendi
Latar Belakang & Ringkasan Kebutuhan: Rangkaian pemeliharaan, refaktorisasi arsitektur status tahapan layanan, dan mitigasi keamanan library pada modul Pengangkut. Perubahan mencakup 4 perbaikan arsitektur dan bisnis yang krusial:
- Sinkronisasi On-Demand Kapal Inaportnet (
feat/update-kapal): Pengadaan endpoint sinkronisasi spesifikasi kapal dari API Inaportnet untuk kapal bertrayek (id_route_type > 0) guna memperbaiki inkonsistensi data identitas dan spesifikasi teknis kapal. - Resolusi Blocker Re-submit & Bypass Status Cetakan Manifest (
feat/superset-summary-optimizations):- Penambahan status kode 47 (Reject Kelengkapan Data Inward), 48 (Reject Kelengkapan Data Outward), dan 49 (Reject Kelengkapan Data RKSP) ke dalam
ALLOWED_STATUS_BY_SERVICEagar pengguna dapat mengedit dan mengirim ulang permohonan (canSend: true) setelah ditolak kelengkapan datanya oleh Bea Cukai. - Implementasi mekanisme
IGNORED_STATUS_BY_SERVICEuntuk status 44 (Cetakan Manifest). Status 44 merupakan aksi pencetakan dokumen PDF perantara dan bukan tahapan persetujuan operasional, sehingga di-bypass pada kalkulasi status terakhir agar tidak menutupi status riil permohonan.
- Penambahan status kode 47 (Reject Kelengkapan Data Inward), 48 (Reject Kelengkapan Data Outward), dan 49 (Reject Kelengkapan Data RKSP) ke dalam
- Standardisasi Status & Controller V2 (
refactor/post-status-helper): Sentralisasi kamus master status seluruh domain pengangkut (iw,ow,rksp,cop,phqc,wkk,wkb) ke dalamapp/constant/statuses.constant.js, migrasi handlerPOST /summary/statuske V2 service layer terpadu (summaryStatusService), serta pemberian anotasi@deprecatedpada fungsi legacypostStatusdanpostStatusAsyncdisuperset_summary_controller.js. - Mitigasi Keamanan DoS (
fix/security-override-brace-expansion): Penerapan npm package overridebrace-expansion: 5.0.8untuk menutup celah keamanan Denial of Service (GHSA-mh99-v99m-4gvg).
- Sinkronisasi On-Demand Kapal Inaportnet (
Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Sub-Pekerjaan 1 — Integrasi Sinkronisasi Kapal Trayek: Implementasi
#mapInaportToRawPayloaddan#prepareShipDataForSavingpadaship.service.jsserta routing endpointPOST /super-set/ship/inaport-sync/:serviceNumber - [x] Sub-Pekerjaan 2 — Optimasi State Engine Superset Summary:
- Konfigurasi
IGNORED_STATUS_BY_SERVICEuntuk melewati status 44 (Cetakan Manifest) pada evaluasigetLatestStatusdangetSummaryByServiceNumber - Konfigurasi
ALLOWED_STATUS_BY_SERVICEuntuk membuka izin kirim ulang (canSend) pada status 47 (Reject Kelengkapan IW), 48 (Reject Kelengkapan OW), dan 49 (Reject Kelengkapan RKSP)
- Konfigurasi
- [x] Sub-Pekerjaan 3 — Sentralisasi Kamus Status & Layanan: Pembentukan modul konstanta
app/constant/statuses.constant.js(master status COP, IW, OW, PHQC, RKSP, WKB, WKK),service-type.constant.js, dansubmission-type.constant.js - [x] Sub-Pekerjaan 4 — Arsitektur Controller V2 Post Status: Implementasi
summaryStatusService.updateStatusyang mendukung single & array payload, pembuatan controller V2app/controllers/v2/summary.controller.js, re-routingPOST /summary/status, serta deprecasi fungsi legacypostStatusdanpostStatusAsyncdisuperset_summary_controller.js - [x] Sub-Pekerjaan 5 — Hardening Security Dependensi: Penguncian versi library
brace-expansionpada versi5.0.8dipackage.jsondan pembaruan dependency treepackage-lock.json
- [x] Sub-Pekerjaan 1 — Integrasi Sinkronisasi Kapal Trayek: Implementasi
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-07-27 10:31 | Hermansyah Efendi | DEPLOYED | Branch: development (Merged from 4 maintenance branches) |
| 2 | Staging (UAT) | 2026-09-09 11:34 | achmad fauzi | DEPLOYED | Release Candidate v1.18.2 via MR !2080. Co-deployed: [TASK-DRAGONFLYDB-MIGRATION, TASK-SPLIT-BILLING-202608, FEAT-MANUAL-BILLING-REQUEST, MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY, REFACTOR-DEPRECATE-POST-STATUS-SERVICE-MIGRATION, TASK-PINO-LOGGER-REFACTOR, ND-304-LNSW.34-2026, ND-256-LNSW.34-2026, ND-183-LNSW.34-2026] |
| 3 | Production | — | — | PENDING | — |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-09-09 | Staging | Promosi ke STAGING via MR !2080 (Release Candidate v1.18.2) | Hermansyah Efendi |
| 2026-09-04 | Development | Initial log created from backend context block | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- Pencegahan Data Stale pada Kapal Bertrayek (
ship.service.js):- Kapal bertrayek (
id_route_type > 0) memiliki master data yang terdaftar di Inaportnet. MethodsyncShipFromInaportmemungkinkan operator menyinkronkan data permohonan secara on-demand langsung dari Inaportnet API jika terjadi perubahan nama kapal, call sign, tonase (GT/DWT), atau pelabuhan asal/tujuan. - Untuk permohonan non-trayek (
id_route_type <= 0), permintaan ditolak dengan pesan yang eksplisit (400 Bad Request).
- Kapal bertrayek (
- Kalkulasi State Dashboard Superset (
superset-summary.service.js):- Selama ini, saat pengguna mengunduh atau mencetak manifest, status berubah menjadi 44 (Cetakan Manifest). Hal ini menyebabkan query evaluasi status terakhir menganggap dokumen masih dalam proses cetak dan mengaburkan status definitif Bea Cukai.
- Dengan filter
AND hsd.status_id NOT IN (:ignoredStatuses)padagetLatestStatusserta filtering digetSummaryByServiceNumber, status 44 di-skip sehingga status riil sebelum/sesudah cetakan tetap terbaca dengan benar. - Pengguna yang mendapat penolakan kelengkapan dokumen Bea Cukai (status 47, 48, 49) kini memiliki hak akses untuk memicu pengiriman ulang dokumen (resubmission) tanpa harus membuat nomor layanan permohonan baru dari awal (canSend: true).
- Penyatuan Endpoint Status (
summary-status.service.js&v2/summary.controller.js):- Pembaruan status permohonan dikonsolidasikan ke method
updateStatus, memvalidasi tipe layanan dan kode status terhadap kamusstatuses.constant.js. - Menggunakan pattern
asyncHandlerdan validasi Zod schema (SummaryPostStatusValidationSchema).
- Pembaruan status permohonan dikonsolidasikan ke method
- Hardening Security Dependensi (
package.json):- Menambahkan blok override pada
package.jsonguna memaksa resolusi dependensibrace-expansionke versi patch aman5.0.8untuk menutup celah DoS (GHSA-mh99-v99m-4gvg).
- Menambahkan blok override pada
- Pencegahan Data Stale pada Kapal Bertrayek (
Matriks Pemetaan Status Kunci (Business Domain Context):
Layanan Kode Status Nama Status Kategori & Perlakuan Sistem IW 44Cetakan Manifest Bypass ( IGNORED_STATUS_BY_SERVICE): Dilewati pada kalkulasi status agar tidak menutupi status persetujuan/penolakan riil.IW 47Reject Kelengkapan Data Inward Whitelisted ( ALLOWED_STATUS_BY_SERVICE): Membuka izin edit & kirim ulang permohonan (canSend: true).OW 44Cetakan Manifest Bypass ( IGNORED_STATUS_BY_SERVICE): Dilewati pada kalkulasi status.OW 48Reject Kelengkapan Data Outward Whitelisted ( ALLOWED_STATUS_BY_SERVICE): Membuka izin edit & kirim ulang permohonan (canSend: true).RKSP 44Cetakan Manifest Bypass ( IGNORED_STATUS_BY_SERVICE): Dilewati pada kalkulasi status.RKSP 49Reject Kelengkapan Data RKSP Whitelisted ( ALLOWED_STATUS_BY_SERVICE): Membuka izin edit & kirim ulang permohonan (canSend: true).COP / PHQC 44Cetakan Billing Pembayaran Cetakan billing Karantina Kesehatan Kemenkes. Daftar File yang Berubah:
- Controller / API Route:
app/controllers/v2/ship.controller.js(HandlersyncShipFromInaport)app/controllers/v2/summary.controller.js(Handler V2postStatusberbasisasyncHandler)app/controllers/kedatangan_inward_controller.js(Migrasi pemanggilan status kesummaryStatusService)app/controllers/superset_summary_controller.js(Deprecate fungsi legacypostStatusdanpostStatusAsync)app/routers/ship_route.js(Registrasi routePOST /super-set/ship/inaport-sync/:serviceNumber)app/routers/superset_summary_route.js(Re-routePOST /summary/statuske V2)
- Service & Constants:
app/services/ship.service.js(Metode#mapInaportToRawPayloaddansyncShipFromInaport)app/services/superset-summary.service.js(Whitelist status 47, 48, 49 & bypass status 44)app/services/summary-status.service.js(Core engine pembaruan status terpusat)app/constant/statuses.constant.js(Kamus konstanta status lengkap 7 layanan pengangkut)app/constant/service-type.constant.js(Enum kode layanan pengangkut)app/constant/submission-type.constant.js(Enum tipe kedatangan / keberangkatan)
- Security & Config:
package.json&package-lock.json(Override dependensibrace-expansion)CHANGELOG.md(Pencatatan rilis perubahan)
- Controller / API Route:
Perubahan Database (DDL / DML):
Tidak ada perubahan skema database (DDL) atau file migrasi SQL pada seluruh branch ini.
Kontrak API & Response Payload:
POST/super-set/ship/inaport-sync/:serviceNumber- Deskripsi: Sinkronisasi on-demand spesifikasi dan identitas kapal dari Inaportnet API ke database lokal.
- Autentikasi / Guard:
Bearer Token(doIntrospection) +checkAccessShipByServiceNumber - Request Header / Query / Params:
Header:Authorization: Bearer <token>Path Param:serviceNumber(string, nomor permohonan AJU)
- Request Body: Tidak ada (eksekusi via path parameter
serviceNumber) - Response Payload (
200 OK):json{ "status": "Success", "code": 200, "message": "Data kapal berhasil disinkronkan dari Inaport", "data": { "id_ship": 105, "service_number": "101026060001", "name": "KM BINTANG SAMUDERA", "flag": "ID", "gross_tonnage": 1500, "deadweight_ton": 2200, "call_sign": "YBD2", "updated_at": "2026-06-03T08:54:02.000Z" } } - Response Error (
400 Bad Request):json{ "status": "Failed", "code": 400, "message": "Sync dari Inaport hanya berlaku untuk kapal trayek!" }
POST/summary/status(V2 Controller) - Deskripsi: Memperbarui status tahapan layanan pengangkut secara terpusat (mendukung single update via HTTP controller dan batch/array via service layer).
- Autentikasi / Guard:
Bearer Token(doIntrospection) - Request Header / Query / Params:
Header:Authorization: Bearer <token>,Content-Type: application/jsonQuery:showAll=true|false(mengembalikan full breakdown status),type=string
- Request Body (Contoh Single Update):json
{ "service_number": "101026060001", "service_type": "iw", "status_id": 47, "status_info": "Reject Kelengkapan Data Inward" } - Request Body (Opsi Batch Array via Service Layer):json
[ "101026060001", "iw", 47, "Reject Kelengkapan Data Inward" ] - Response Payload (
200 OK):json{ "status": "Success", "code": 200, "message": "Status berhasil diperbarui", "data": { "serviceNumber": "101026060001", "serviceType": "iw", "statusId": 47, "statusInfo": "Reject Kelengkapan Data Inward" } } - Response Error (
400 Bad Request):json{ "status": "Failed", "code": 400, "message": "service number is cannot to be empty" }
4. Bukti Teknis (Technical Proof & Audit Trail)
- Repository:
pengangkut-backend - Branch Utama:
development - Merge Request / PR: MR !2080 (Release Candidate v1.18.2)
Daftar Commit Terkait (Multi-Commit Trail)
| Commit ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
3bf76f0bd94cf79e504333f7d9a7502730f6a116 | fix/security-override-brace-expansion | Hermansyah Efendi | 2026-07-27 10:31 | fix(security): override brace-expansion to v5.0.8 to fix DoS vulnerability |
dc9883dd70ffb6b906b3b6d6459bb3eac80d9159 | refactor/post-status-helper | Hermansyah Efendi | 2026-07-07 15:25 | refactor(summary): migrate status update logic to service layer and v2 controller |
cdb0b3e9867cc40da5d6f9c2150acc34ebd3dacc | refactor/post-status-helper | Hermansyah Efendi | 2026-07-07 15:11 | feat(constant): add service, status, and submission type constants |
27fa8bb78e8f3aaed22bb2e90141ef06dcdc0756 | feat/superset-summary-optimizations | Hermansyah Efendi | 2026-06-17 15:53 | refactor(superset-summary): centralize allowed statuses and add ignored status bypass |
d30edb3cf2561b83fd9e9e9ab127b17efe8ca480 | feat/update-kapal | Hermansyah Efendi | 2026-06-03 08:54 | feat(ship): add sync ship from inaport endpoint and optimize upsert override |
Cuplikan Kode / Implementasi Inti
1. Penanganan Whitelist & Bypass Status Transisi
// app/services/superset-summary.service.js
const STATUS_IW_REJECT_COMPLETENESS = 47;
const STATUS_OW_REJECT_COMPLETENESS = 48;
const STATUS_RKSP_REJECT_COMPLETENESS = 49;
const ALLOWED_STATUS_BY_SERVICE = {
iw: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED, STATUS_IW_REJECT_COMPLETENESS],
ow: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED, STATUS_OW_REJECT_COMPLETENESS],
rksp: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED, STATUS_RKSP_REJECT_COMPLETENESS],
wkk: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED],
wkb: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED],
cop: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED],
phqc: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED],
};
// Status 44 (Cetakan Manifest) di-bypass agar tidak menimpa status operasional sebelumnya
const IGNORED_STATUS_BY_SERVICE = {
iw: [44],
ow: [44],
rksp: [44],
};2. Logika Sinkronisasi Kapal Inaportnet
// app/services/ship.service.js
async syncShipFromInaport(serviceNumber, auth, headers) {
const userId = auth.userId;
const dataAgent = await agentService.findOneByServiceNumber(serviceNumber);
if (!dataAgent) throw new NotFoundError('AJU tidak ditemukan!');
if (dataAgent.id_route_type <= 0) {
throw new BadRequestError('Sync dari Inaport hanya berlaku untuk kapal trayek!');
}
const trayek = this.getTrayekNameById(dataAgent.id_route_type);
const existingShip = await this.findOneByServiceNumber(serviceNumber);
const registrationNumber = dataAgent.registration_ship_number;
const ship = await this.postDataFunction(trayek, registrationNumber, userId, headers, auth);
const rawPayload = {
serviceNumber,
trayek,
registrationNumber,
identity: {},
specification: {},
ports: {},
dates: {},
isOutward: serviceNumber.startsWith('102'),
userId
};
this.#mapInaportToRawPayload(ship, rawPayload);
const preparedData = this.#prepareShipDataForSaving(rawPayload);
await Ship.update({
...preparedData,
updated_by: userId,
updated_at: new Date()
}, {
where: { id_ship: existingShip.id_ship }
});
return await this.getDataShipSpecific({ serviceNumber });
}3. Controller V2 Update Status
// app/controllers/v2/summary.controller.js
export const postStatus = asyncHandler(async (req, res) => {
await parseWithZod(SummaryPostStatusValidationSchema, req.body);
const { service_number, service_type, status_id, status_info } = req.body;
const { showAll, type } = req.query;
const data = await summaryStatusService.updateStatus({
service_number,
service_type,
status_id,
status_info,
type,
});
if (showAll === 'true') {
req.query.serviceNumber = service_number;
return getAllSummaryByServiceNumber(req, res);
}
res.sendJson(summaryStatusService.humanizeResult(data));
});4. Mitigasi Keamanan Dependensi (Override brace-expansion)
// package.json
"overrides": {
"uuid": "^14.0.0",
"brace-expansion": "^5.0.8"
}5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification)
A. Verifikasi Unit Test (Development Stage)
(Opsional: Dilampirkan jika task mencakup penambahan/perubahan unit test)
- Test Suite File: Tidak ada file unit test baru disertakan pada commit pemeliharaan ini
- Status Eksekusi CLI:
SKIPPED
// Tidak ada file unit test baru disertakan pada commit pemeliharaan ini.
// Pengujian difokuskan pada integrasi manual via Postman dan QA Playbook.B. Panduan Pengujian QA & Verifikasi Staging (QA Playbook)
Diisi saat development sebagai panduan eksekusi pengujian QA, kemudian dilengkapi bukti response JSON saat verifikasi di Staging.
- Cakupan / Surface Pengujian:
API / Postman Only & Full E2E (UI + Backend) - Kebutuhan Akses / Role Akun: Token sesi aktif, akun agen pelayaran berwenang, permohonan kapal trayek (
id_route_type > 0), dan permohonan kapal non-trayek (id_route_type <= 0). - Hasil Pengujian UAT:
PENDING - Catatan QA: —
Skenario Pengujian QA (Test Scenarios)
Skenario 1 — Happy Path Sinkronisasi Kapal Trayek dari Inaportnet:
- Pre-kondisi: Permohonan AJU kapal trayek terdaftar dengan status aktif (
id_route_type > 0). - Trigger / Request:
POST /super-set/ship/inaport-sync/:serviceNumberdengan Bearer Token valid. - Ekspektasi Response: HTTP
200 OKdengan pesan "Data kapal berhasil disinkronkan dari Inaport". - Verifikasi Database / Log: Record pada tabel
pengangkut.shipster-update dengan atribut mutakhir dari Inaportnet (updated_atbernilai waktu eksekusi).
- Pre-kondisi: Permohonan AJU kapal trayek terdaftar dengan status aktif (
Skenario 2 — Negative Case Sinkronisasi Kapal Non-Trayek:
- Pre-kondisi: Permohonan AJU memiliki
id_route_type <= 0(Kapal Non-Trayek). - Trigger / Request:
POST /super-set/ship/inaport-sync/:serviceNumber. - Ekspektasi Response: HTTP
400 Bad Requestdengan pesan error: "Sync dari Inaport hanya berlaku untuk kapal trayek!". - Verifikasi Database / Log: Tidak ada perubahan record pada tabel
pengangkut.ships.
- Pre-kondisi: Permohonan AJU memiliki
Skenario 3 — Validasi Bypass Status 44 (Cetakan Manifest) pada Superset Summary:
- Pre-kondisi: Permohonan memiliki riwayat status 40 (Disetujui), kemudian sistem mencatat status 44 (Cetakan Manifest).
- Trigger / Request: Panggil endpoint summary
GET /summary?serviceNumber=:serviceNumber. - Ekspektasi Response: Ringkasan status layanan menampilkan status 40, bukan status 44, karena status 44 terdaftar pada
IGNORED_STATUS_BY_SERVICE. - Verifikasi Database / Log: Query summary otomatis mengecualikan status transisi (
status_id NOT IN (44)).
Skenario 4 — Izin Kirim Ulang Permohonan Saat Status 47/48/49 (Reject Kelengkapan Data):
- Pre-kondisi: Permohonan Inward mendapatkan status 47 (Reject Kelengkapan Data Inward).
- Trigger: Evaluasi properti
canSendpada summary response permohonan. - Ekspektasi:
canSendbernilaitrue(sebelumnya bernilaifalse), sehingga tombol kirim ulang di antarmuka web aktif dan pengguna dapat melakukan submit perbaikan. - Verifikasi Database / Log: Logika evaluasi status merujuk pada elemen baru
STATUS_IW_REJECT_COMPLETENESSdi arrayALLOWED_STATUS_BY_SERVICE.iw.
Skenario 5 — Pembaruan Status Layanan Menggunakan Controller V2:
- Pre-kondisi: Permohonan aktif dalam status awal.
- Trigger / Request:
POST /summary/statusdengan payload JSON{ service_number, service_type: "iw", status_id: 47, status_info: "Reject Kelengkapan Data Inward" }. - Ekspektasi Response: HTTP
200 OKdengan pesan "Status berhasil diperbarui". - Verifikasi Database / Log: Record status tersimpan pada tabel tracking log permohonan.
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PENDING",
"note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA."
}