15.4 Correctness & data integrity debt
บทนี้รวบรวม debt ที่กระทบความถูกต้องของข้อมูล — ปัญหาที่อาจทำให้ข้อมูลไม่สมบูรณ์ ไม่ consistent หรือ authorization ตัดสินใจผิด
รายการทั้งหมด
CORR-1 · Cross-schema write ไม่มี transaction boundary
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/connector.js:8-48 — connection db (schema vtrc) + db2 (schema vtrc-centralize); models/centralize/user เขียน db2 |
| ปัญหา | vtrc-api เขียนข้อมูลกระจายสอง schema โดยไม่มี transaction boundary ระหว่างสอง schema — ถ้าเกิด failure ระหว่าง write แรกกับ write ที่สอง ข้อมูลจะไม่ consistent |
| Impact | ข้อมูลครึ่งเขียน (partial write) — เช่น user ถูกสร้างใน schema vtrc แต่ profile ไม่ถูกสร้างใน vtrc-centralize การ debug ยาก |
| Mitigation ระยะสั้น | ทำคู่ manual rollback ใน catch block (best-effort) — ไม่สมบูรณ์แต่ดีกว่าไม่มี |
| แนวทางระยะยาว | ถ้าจำเป็นต้อง cross-schema write ใช้ XA / 2-phase commit หรือ saga pattern ที่มี compensating action |
CORR-2 · Transaction ถูก swallow ใน session repository
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/lib/repositories/session/session.js:79-88, 104-118 — pattern Models.db.transaction().then(...) ที่ใน .catch เรียก t.rollback() แล้ว responError() (ซึ่ง throw) แต่ outer .then ยังทำงานต่อและอาจ t.commit() บน transaction ที่ rollback แล้ว |
| ปัญหา | race condition — ถ้า Models.Session.create reject, .catch rollback แล้ว throw แต่ outer .then อาจ commit บน transaction ที่ rollback แล้ว |
| Impact | session อาจไม่ถูกบันทึกจริง ๆ ทั้งที่ดูเหมือน login สำเร็จ — ผู้ใช้พบ "session not found" ใน request ถัดไป |
| Mitigation ระยะสั้น | เปลี่ยนไปใช้ async/await + try/catch/finally ที่ชัดเจน |
| แนวทางระยะยาว | ในภาษาที่มี error เป็น value (เช่น Go) ไม่มีปัญหาแบบนี้ |
CORR-3 · responError throw เสมอ แต่ caller เขียนต่อ
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/lib/responErrors.js:3-29 — throw ทุก path; caller ทั่ว codebase เขียน responError(...); return result; (dead code) |
| ปัญหา | โครงสร้างควบคุมหลอก — ผู้อ่านคิดว่า return result รันหลัง responError แต่จริง ๆ ไม่เคยรัน ที่แย่กว่า: try/catch บางแห่ง catch ApolloError ที่ throw ออกมาแล้ว swallow เป็น "Something went wrong" |
| Impact | error จริงถูกซ่อน debug ยาก ผู้ใช้เห็น error message ที่ไม่เกี่ยวข้อง |
| Mitigation ระยะสั้น | เอกสารชัดเจนว่า responError throw — เพิ่ม lint rule ที่ตรวจ code หลัง responError |
| แนวทางระยะยาว | ใช้ typed error ที่ return ไม่ throw |
CORR-4 · ตรรกะ RBAC ไม่สม่ำเสมอ
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/lib/repositories/session/session.js:130 vs session.js:55 |
| ปัญหา | getCurrentRoleUser (บรรทัด 130) เปรียบเทียบ Role.roleName แต่ checkMenuAuth (บรรทัด 55) อ่าน userResult[0].role.accessRole ซึ่งเป็น property ที่ ไม่มีอยู่จริง บน JSON blob role (ที่เก็บ roleIds) — RBAC อาจ authorize ผิดโดยไม่มี error |
| Impact | ผู้ใช้บางคนอาจได้สิทธิ์ที่ไม่ควรได้ หรือไม่ได้สิทธิ์ที่ควรได้ — ปัญหาเงียบที่ตรวจจับยาก |
| Mitigation ระยะสั้น | เขียน test matrix (role × menu × action) ทุกกรณี แล้วแก้ source ของ truth ให้เป็นทางเดียว |
| แนวทางระยะยาว | ใช้ Casbin + policy test สำหรับทุก (role × menu × action) |
CORR-5 · centralize-api log SQL ใน production
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | centralize-api/src/app.module.ts:34,53 — logging: ['error', 'query'] |
| ปัญหา | production log เก็บ query ทั้งหมด (อาจมี PII) ที่ระดับ query — เปลือง disk และเพิ่มความเสี่ยง PII |
| Impact | disk เต็มเร็ว, PII รั่วใน log |
| Mitigation ระยะสั้น | เปลี่ยนเป็น logging: ['error'] ใน production |
| แนวทางระยะยาว | ใช้ sampling/tracing แทน logging query ทั้งหมด |
CORR-6 · syncModelToTable รันทุกครั้งที่ boot
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/index.js:104 — เรียก syncModelToTable() |
| ปัญหา | Sequelize auto-sync รันกับ production table ทุกครั้ง — ถ้า model เปลี่ยนแล้ว deploy โดยไม่ตั้งใจ schema ของ production จะถูกแก้โดยไม่มี migration review |
| Impact | การ deploy อาจทำให้ข้อมูลหาย (column drop) หรือ schema เปลี่ยนกลายเป็น incident |
| Mitigation ระยะสั้น | ตรวจ IS_PRODUCTION ก่อนเรียก syncModelToTable — ใน prod ใช้ migration เท่านั้น |
| แนวทางระยะยาว | ใช้ migration tool ที่ forward-only (golang-migrate, goose, atlas) |
CORR-7 · คอลัมน์ซ้ำใน Sequelize model
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/models/core/withdraw/hospital.js:14,138 — coaCodeName ประกาศสองครั้ง; :159,189 — isJuristic ประกาศสองครั้ง |
| ปัญหา | Sequelize เก็บ definition สุดท้าย — definition แรกเป็น dead code ผู้ที่อ่าน source จะถูกหลอก |
| Impact | ความเข้าใจผิดในการ maintain การแก้ definition แรกไม่มีผล |
| Mitigation ระยะสั้น | เปิด ESLint rule no-dupe-keys ลบ declaration ที่ซ้ำ |
| แนวทางระยะยาว | ในภาษาที่มี type (เช่น Go) struct field ซ้ำ = compile error |
CORR-8 · PDF deletion race
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | vtrc-api/api/src/routes.js:79-82 — setTimeout(deletePDF, 5000) หลัง res.sendFile |
| ปัญหา | ถ้า download ช้า (มากกว่า 5 วินาที) ไฟล์จะถูกลบระหว่าง stream — download ล้มเหลว |
| Impact | ผู้ใช้ที่อยู่บนเครือข่ายช้าจะดาวน์โหลด PDF ไม่สำเร็จ |
| Mitigation ระยะสั้น | ใช้ onFinish hook ของ stream แทน timer |
| แนวทางระยะยาว | เก็บ PDF ใน storage ที่มี lifecycle policy (เช่น signed URL ที่หมดอายุ) |
CORR-9 · httpRequest.js พังใน cu-central-api
| ข้อมูล | รายละเอียด |
|---|---|
| Evidence | cu-central-api/main-api/src/lib/httpRequest.js:1,19 — import axios ที่ไม่ได้ประกาศ |
| ปัญหา | ไฟล์นี้รันไม่ได้ตามที่เขียน — ถ้า code path ใดเรียกมันใน production service จะ crash |
| Impact | ปัญหาเงียบที่อาจไม่เคยเกิด (dead code) หรือเกิดเป็น incident กลางอากาศ |
| Mitigation ระยะสั้น | แก้ import ให้ถูกต้อง หรือลบไฟล์ถ้าไม่มี caller |
| แนวทางระยะยาว | ใช้ typed HTTP client ที่ตรวจสอบ type ตอน compile |
สรุป
| ID | รายการ | Severity | Effort ระยะสั้น |
|---|---|---|---|
| CORR-1 | cross-schema write | Critical | — (architectural) |
| CORR-2 | transaction swallow | High | 1-2 วัน |
| CORR-3 | responError throw pattern | High | 1 สัปดาห์ (lint + refactor) |
| CORR-4 | RBAC ไม่สม่ำเสมอ | Critical | 1-2 สัปดาห์ (test matrix) |
| CORR-5 | centralize log query | High | 30 นาที |
| CORR-6 | syncModelToTable | Critical | 1 ชั่วโมง |
| CORR-7 | คอลัมน์ซ้ำ | High | 30 นาที |
| CORR-8 | PDF deletion race | Med | 1 วัน |
| CORR-9 | httpRequest.js พัง | High | 30 นาที |
บทถัดไปจะไปยัง debt ด้าน runtime/framework EOL ซึ่งเป็นพื้นฐานของความเสี่ยงด้านความปลอดภัยในระยะยาว