[Tracker] QA Brain Integration — Collaborative QA Institutional Memory (MVP Phase 1 & 2)
สรุปภาพรวม (Overview)
แผนงานบูรณาการ QA Brain (คลังบทเรียนและ Gotchas ของ QA) เข้ากับ Happy QA Platform & MCP เพื่อให้คนในทีมและ AI Agent ทุกตัวสามารถค้นหาและร่วมกันบันทึกบทเรียนการทดสอบได้อย่างปลอดภัย
เอกสารอ้างอิง: Canonical RFC v4.1 (Implementation-Ready Core Spec) แนบอยู่ส่วนท้ายของ Issue นี้อย่างสมบูรณ์
เป้าหมายของ MVP (Phase 1 & 2)
- กำจัดคอขวด Single-Writer PGLite ท้องถิ่น: ย้ายคลังความรู้เข้าสู่ PostgreSQL กลางของ Happy QA
-
เปิดรับทั้งคนและ Agent: ให้ Agent (Claude Code, ChatGPT, Cursor, AIEM) และคนสามารถบันทึก Gotchas ได้ โดยเริ่มต้นสถานะเป็น
DRAFTเสมอ -
ระบบสิทธิ์และ Audit รัดกุม (Omnipresent Scope Authorization & Immutable Audit Trail): กรองสิทธิ์ก่อนค้นหาและก่อนส่งข้อมูลกลับในทุกมิติ พร้อมลงบันทึกใน
qa_audit_eventด้วย Hash Chain - นำเข้าข้อมูลเดิม 70+ รายการ: ผ่านกระบวนการ Dry-Run และ Seeding อย่างปลอดภัยโดยไม่เขียนทับข้อมูลที่มีคนแก้ไข
แผนผังลำดับการพัฒนา (Dependency Graph & Execution Order)
T1: Data Model & DB Constraints (#1)
│
├──> T2: Pre-Ingress Sanitizer (#2)
│ │
│ ├──> T3: Atomic Idempotency (#3) ──┐
│ │ │
│ ├──> T4: Scoped Query Service (#4) ───> T6: MCP Tools Catalogue (#6)
│ │ │
│ └──> T5: CAS Web Domain (#5) ──────┼──> T7: Web UI & Verification (#7)
│ │
├──> T8: Vault Seeding Tool (#8) │
│ │
└──> T9: Security & E2E Test Suite (#9) <─┘
│
└──> T10: RFC v4.1 Coverage Closure & Hardening (#11) [Audit / Typed Evidence / Duplicate Advisory / Contract Alignment]
รายการ Implementation Tickets (T1 - T10)
- #1 - T1: Data Model, Composite Relations, and SQL Check Constraints
- #2 - T2: Pre-Ingress Sanitizer (v1.0.0) & Request Fingerprinting Pipeline
- #3 - T3: Atomic Transactional Idempotency Engine for Brain Mutations
- #4 - T4: Case-Insensitive Substring/Keyword Search Baseline & Query Service
- #5 - T5: Web Domain Commands with CAS Concurrency & Promotion Copy Engine
- #6 - T6: MCP Tool Catalogue Expansion & Ports Implementation
- #7 - T7: Web UI Feed, Quick Entry Modal, and Lead Verification Dashboard
- #8 - T8: Dry-Run Audit & Historical Vault Seeding Tool
- #9 - T9: Security, Concurrency, and End-to-End Persona Verification Test Suite
- #11 - T10: RFC v4.1 Coverage Closure & Hardening
เกณฑ์การตรวจรับรอง (Acceptance Gate)
- ผ่านชุดการทดสอบความปลอดภัยและการทำงานร่วมกัน 15 ด่านใน #9 ครบ 100%
- ผ่าน End-to-End Persona Acceptance Test (Agent A บันทึก $\rightarrow$ Lead กดรับรอง $\rightarrow$ Agent B ใน Session ใหม่ค้นเจอและหลบกับดักสำเร็จ)
Canonical RFC v4.1: Collaborative QA Institutional Memory Architecture
1. ที่มาและปัญหาเดิม (Background & Core Problems)
- P-1: Agent ลืม ไม่ใช่คนลืม — เจอ Gotcha ในโปรเจกต์หนึ่ง พอเปิด Session ใหม่ Agent ไม่มีความรู้นั้นหลงเหลืออยู่
- P-2: ไม่มีคลังกลาง — บทเรียนและ Gotcha กระจัดกระจายตามเครื่องของแต่ละคน
- P-3: การจดเป็นภาระของมนุษย์ — หากไม่มีกลไกดักจับ ความรู้จะสูญหาย
- เกณฑ์สำเร็จ: "เปลี่ยนตัว Agent แล้วยังทำงานต่อตามระบบได้โดยไม่ทำข้อผิดพลาดซ้ำ"
ข้อเท็จจริงและข้อจำกัดปัจจุบัน
- คลังเดิมผูกติดกับเครื่องส่วนบุคคลและติดคอขวด PGLite Single-Writer
- Gotcha เดิมเกิดจาก Stop Hook ท้องถิ่นบนเครื่อง Mac เครื่องเดียว
- Happy QA MCP ยึดหลัก Closed Data-Plane และ Governance เข้มงวด
2. หลักการและสัญญาระบบที่ล็อคใน RFC v4.1
2.1 ยุทธศาสตร์การค้นหาภาษาไทย (Thai Search Baseline)
- MVP ใช้ Case-insensitive Substring / Keyword Search (ILIKE) บน
title,what,evidenceRef,tagsเป็น Baseline - รันชุดทดสอบวัดผล Benchmark ใน Phase 3 ก่อนพิจารณา Tokenizer หรือ Semantic Vector
2.2 วงจรการรับรองผูกกับ Revision และ CAS
- ทุกคำสั่ง Edit, Verify, Deprecate, Promote ต้องส่ง
expectedVersion - หากมี Version Conflict ตอบกลับ HTTP 409
VERSION_CONFLICT - การแก้ไขข้อมูลสำคัญปลดสถานะกลับเป็น
DRAFTเสมอ และล้างข้อมูลverifiedByUserId - ประวัติการแก้ไขบันทึกลง
qa_brain_revisionและส่ง Event เข้าqa_audit_event
2.3 การยกระดับสู่ส่วนกลางแบบ Promoted Copy (Promotion Lifecycle)
- ไม่ทำลายต้นฉบับเดิมในโปรเจกต์
- คำสั่ง
promote: สร้างแถวใหม่projectId = null,promotedFromGotchaId = source.id,sourceGotchaRevision = source.version,status = DRAFT,approvedRevision = null,promotedAt = null - บังคับส่ง Neutralized Evidence ในคำขอ Promote ห้ามคัดลอกข้อมูลเฉพาะโปรเจกต์อัตโนมัติ
- สิทธิ์การอนุมัติและ Publish:
WORKSPACE_QA_LEADหรือWORKSPACE_ADMINเท่านั้น - เมื่อ Lead กดยืนยัน Verify: ระบบจะรัน CAS อัปเดต
status = VERIFIED,approvedRevision = version, และpromotedAt = now()
2.4 การตีกรอบ DRAFT อย่างเคร่งครัด
-
query: ส่งคืนเฉพาะVERIFIEDโดยตั้งต้น การอ่าน Draft ต้องส่งincludeDraft: trueและมีสิทธิ์QA_EDITOR+ -
get_entry: หากขอ DRAFT โดยไม่มีสิทธิ์ ตอบกลับNOT_FOUNDทันที
2.5 นิยามความหมายของ projectId 3 รูปแบบ (3-Tier Project Semantics)
-
ไม่ระบุ
projectId(Omitted / Undefined): คืน Gotcha ส่วนกลางของ Workspace รวมกับทุกโปรเจกต์ที่ได้รับ Grant (workspace_id = :wsId AND (project_id IS NULL OR project_id IN (:authorizedProjectIds))) -
ระบุ
projectId: nullอย่างชัดแจ้ง: คืนเฉพาะ Gotcha ส่วนกลางระดับ Workspace เท่านั้น (workspace_id = :wsId AND project_id IS NULL) -
ระบุ ID โปรเจกต์ (
projectId = "proj-123"): ตรวจสอบสิทธิ์ก่อน $\rightarrow$ คืน Gotcha ของโปรเจกต์นั้น รวมกับ Gotcha ส่วนกลาง (workspace_id = :wsId AND (project_id = 'proj-123' OR project_id IS NULL))
- Machine Key อ่าน Gotcha ส่วนกลางได้ แต่อ่านข้ามโปรเจกต์ที่ไม่ได้รับ Grant ไม่ได้
2.6 Pre-Ingress Sanitizer (Policy v1.0.0) & Request Fingerprinting
- ทุก Write Path (ทั้ง MCP
record_gotcha, Webcreate, และ Webedit) ต้องผ่าน Sanitizer Policy v1.0.0 - Mask Secrets (
[REDACTED_SECRET]) และ PII ([REDACTED_PII]) - ห้าม Outbound HTTP Requests (SSRF Immunity)
- Fail-Closed หาก Sanitizer ขัดข้อง
- Fingerprint คำนวณจาก Canonical Sanitized Payload
2.7 Concurrent Idempotency & Immutable Audit Trail
-
Atomic Idempotency Resolution: ใช้ Transaction-level Lock (
SELECT pg_advisory_xact_lock(...)หรือ row-level lock) เพื่อให้คำขอขนานกันเข้าคิวเรียงหนึ่ง คำขอแรกทำหน้าที่สร้างข้อมูล (First Winner) คำขอที่เหลือรอและได้รับผลลัพธ์ Replay เดิม ป้องกัน Unique Constraint Violation Error 100% -
Audit Trail Integration: เพิ่ม
"brain"ในQA_WORKSPACE_EVENT_FAMILIESทุก Mutation บันทึกผ่านappendAuditพร้อมต่อ Hash Chain ต่อเนื่อง
3. โครงสร้างฐานข้อมูล (Prisma Models)
enum QaBrainGotchaStatus {
DRAFT
VERIFIED
DEPRECATED
}
enum QaBrainChannel {
WEB_UI
MCP_DELEGATED
MCP_MACHINE
IMPORT_SEED
}
enum QaBrainAuthorType {
HUMAN
AGENT
UNKNOWN
}
enum QaBrainEvidenceType {
RUN_RESULT
GIT_COMMIT
ISSUE
LOG_EXCERPT
}
enum QaBrainPrincipalType {
USER
MACHINE
}
model QaBrainGotcha {
id String @id @default(cuid())
workspaceId String @map("workspace_id")
projectId String? @map("project_id")
title String
slug String
what String @db.Text
evidenceType QaBrainEvidenceType @map("evidence_type")
evidenceRef String @map("evidence_ref") @db.Text
evidenceDetail String? @map("evidence_detail") @db.Text
howToApply String @map("how_to_apply") @db.Text
tags String[]
contentHash String @map("content_hash")
channel QaBrainChannel @map("channel")
authorType QaBrainAuthorType @default(HUMAN) @map("author_type")
agentName String? @map("agent_name")
agentVersion String? @map("agent_version")
actorUserId String? @map("actor_user_id")
machineKeyId String? @map("machine_key_id")
lastMutationChannel QaBrainChannel @default(IMPORT_SEED) @map("last_mutation_channel")
manuallyModifiedAt DateTime? @map("manually_modified_at")
status QaBrainGotchaStatus @default(DRAFT)
verifiedByUserId String? @map("verified_by_user_id")
verifiedAt DateTime? @map("verified_at")
verifiedRevision Int? @map("verified_revision")
promotedFromGotchaId String? @map("promoted_from_gotcha_id")
sourceGotchaRevision Int? @map("source_gotcha_revision")
approvedRevision Int? @map("approved_revision")
promotedAt DateTime? @map("promoted_at")
sourceRepo String? @map("source_repo")
sourcePath String? @map("source_path")
sourceFileChecksum String? @map("source_file_checksum")
version Int @default(1)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
workspace QaWorkspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
project QaProject? @relation(fields: [workspaceId, projectId], references: [workspaceId, id], onDelete: Cascade)
promotedFrom QaBrainGotcha? @relation("GotchaPromotionLineage", fields: [workspaceId, promotedFromGotchaId], references: [workspaceId, id], onDelete: Restrict)
promotedCopies QaBrainGotcha[] @relation("GotchaPromotionLineage")
revisions QaBrainRevision[]
@@unique([workspaceId, id])
@@unique([workspaceId, slug])
@@unique([workspaceId, sourceRepo, sourcePath])
@@index([workspaceId, projectId, status])
@@map("qa_brain_gotcha")
}
model QaBrainRevision {
id String @id @default(cuid())
workspaceId String @map("workspace_id")
gotchaId String @map("gotcha_id")
revisionNumber Int @map("revision_number")
title String
what String @db.Text
evidenceType QaBrainEvidenceType @map("evidence_type")
evidenceRef String @map("evidence_ref") @db.Text
evidenceDetail String? @map("evidence_detail") @db.Text
howToApply String @map("how_to_apply") @db.Text
tags String[]
status QaBrainGotchaStatus
authorType QaBrainAuthorType @default(HUMAN) @map("author_type")
agentName String? @map("agent_name")
agentVersion String? @map("agent_version")
changedByUserId String? @map("changed_by_user_id")
machineKeyId String? @map("machine_key_id")
changeReason String? @map("change_reason") @db.Text
createdAt DateTime @default(now()) @map("created_at")
gotcha QaBrainGotcha @relation(fields: [workspaceId, gotchaId], references: [workspaceId, id], onDelete: Restrict)
@@unique([gotchaId, revisionNumber])
@@index([workspaceId, gotchaId])
@@map("qa_brain_revision")
}
model QaBrainIdempotencyKey {
id String @id @default(cuid())
workspaceId String @map("workspace_id")
principalType QaBrainPrincipalType @map("principal_type")
principalId String @map("principal_id")
operation String
key String
sanitizerPolicyVer String @default("1.0.0") @map("sanitizer_policy_ver")
requestFingerprint String @map("request_fingerprint")
sanitizedPayloadHash String @map("sanitized_payload_hash")
responseSnapshot Json @map("response_snapshot")
expiresAt DateTime @map("expires_at")
createdAt DateTime @default(now()) @map("created_at")
@@unique([workspaceId, principalType, principalId, operation, key])
@@index([expiresAt])
@@map("qa_brain_idempotency_key")
}
4. SQL Check Constraints
-- 1. Principal สอดคล้องกับ Channel
ALTER TABLE qa_brain_gotcha ADD CONSTRAINT chk_brain_principal_channel CHECK (
(channel = 'MCP_MACHINE' AND machine_key_id IS NOT NULL AND actor_user_id IS NULL) OR
(channel IN ('WEB_UI', 'MCP_DELEGATED') AND actor_user_id IS NOT NULL AND machine_key_id IS NULL) OR
(channel = 'IMPORT_SEED' AND actor_user_id IS NULL AND machine_key_id IS NULL)
);
-- 2. สถานะ VERIFIED ต้องมีผู้รับรองและ Revision สอดคล้อง
ALTER TABLE qa_brain_gotcha ADD CONSTRAINT chk_brain_verified_invariants CHECK (
(status = 'VERIFIED' AND verified_by_user_id IS NOT NULL AND verified_at IS NOT NULL AND verified_revision IS NOT NULL AND verified_revision = version) OR
(status != 'VERIFIED')
);
-- 3. Provenance ต้องครบชุดหรือว่างครบชุด
ALTER TABLE qa_brain_gotcha ADD CONSTRAINT chk_brain_source_provenance CHECK (
(source_repo IS NULL AND source_path IS NULL AND source_file_checksum IS NULL) OR
(source_repo IS NOT NULL AND source_path IS NOT NULL AND source_file_checksum IS NOT NULL)
);
-- 4. ขยาย Event Check รองรับ brain. ระดับเวิร์กสเปซ
ALTER TABLE "qa_audit_event" DROP CONSTRAINT "qa_audit_event_workspace_level_check";
ALTER TABLE "qa_audit_event" ADD CONSTRAINT "qa_audit_event_workspace_level_check"
CHECK ("project_id" IS NOT NULL
OR starts_with("operation", 'workspace_credential.')
OR starts_with("operation", 'task.vocabulary.')
OR starts_with("operation", 'tracker.')
OR starts_with("operation", 'brain.'));