Skip to content

15.4 Correctness & data integrity debt

บทนี้รวบรวม debt ที่กระทบความถูกต้องของข้อมูล — ปัญหาที่อาจทำให้ข้อมูลไม่สมบูรณ์ ไม่ consistent หรือ authorization ตัดสินใจผิด

รายการทั้งหมด

CORR-1 · Cross-schema write ไม่มี transaction boundary

ข้อมูลรายละเอียด
Evidencevtrc-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

ข้อมูลรายละเอียด
Evidencevtrc-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 แล้ว
Impactsession อาจไม่ถูกบันทึกจริง ๆ ทั้งที่ดูเหมือน login สำเร็จ — ผู้ใช้พบ "session not found" ใน request ถัดไป
Mitigation ระยะสั้นเปลี่ยนไปใช้ async/await + try/catch/finally ที่ชัดเจน
แนวทางระยะยาวในภาษาที่มี error เป็น value (เช่น Go) ไม่มีปัญหาแบบนี้

CORR-3 · responError throw เสมอ แต่ caller เขียนต่อ

ข้อมูลรายละเอียด
Evidencevtrc-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"
Impacterror จริงถูกซ่อน debug ยาก ผู้ใช้เห็น error message ที่ไม่เกี่ยวข้อง
Mitigation ระยะสั้นเอกสารชัดเจนว่า responError throw — เพิ่ม lint rule ที่ตรวจ code หลัง responError
แนวทางระยะยาวใช้ typed error ที่ return ไม่ throw

CORR-4 · ตรรกะ RBAC ไม่สม่ำเสมอ

ข้อมูลรายละเอียด
Evidencevtrc-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

ข้อมูลรายละเอียด
Evidencecentralize-api/src/app.module.ts:34,53logging: ['error', 'query']
ปัญหาproduction log เก็บ query ทั้งหมด (อาจมี PII) ที่ระดับ query — เปลือง disk และเพิ่มความเสี่ยง PII
Impactdisk เต็มเร็ว, PII รั่วใน log
Mitigation ระยะสั้นเปลี่ยนเป็น logging: ['error'] ใน production
แนวทางระยะยาวใช้ sampling/tracing แทน logging query ทั้งหมด

CORR-6 · syncModelToTable รันทุกครั้งที่ boot

ข้อมูลรายละเอียด
Evidencevtrc-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

ข้อมูลรายละเอียด
Evidencevtrc-api/api/src/models/core/withdraw/hospital.js:14,138coaCodeName ประกาศสองครั้ง; :159,189isJuristic ประกาศสองครั้ง
ปัญหา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

ข้อมูลรายละเอียด
Evidencevtrc-api/api/src/routes.js:79-82setTimeout(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

ข้อมูลรายละเอียด
Evidencecu-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รายการSeverityEffort ระยะสั้น
CORR-1cross-schema writeCritical— (architectural)
CORR-2transaction swallowHigh1-2 วัน
CORR-3responError throw patternHigh1 สัปดาห์ (lint + refactor)
CORR-4RBAC ไม่สม่ำเสมอCritical1-2 สัปดาห์ (test matrix)
CORR-5centralize log queryHigh30 นาที
CORR-6syncModelToTableCritical1 ชั่วโมง
CORR-7คอลัมน์ซ้ำHigh30 นาที
CORR-8PDF deletion raceMed1 วัน
CORR-9httpRequest.js พังHigh30 นาที

บทถัดไปจะไปยัง debt ด้าน runtime/framework EOL ซึ่งเป็นพื้นฐานของความเสี่ยงด้านความปลอดภัยในระยะยาว