690517:1449 204 and 302 refactor #03
This commit is contained in:
+2
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
> **The Event Horizon of Software Quality.**
|
||||
> _Adapted for Google Antigravity IDE from [github/spec-kit](https://github.com/github/spec-kit)._
|
||||
>
|
||||
> # Speckit Agent Infrastructure (v1.9.0)
|
||||
>
|
||||
> - Version: 1.9.0
|
||||
@@ -282,7 +283,7 @@ If you change your mind mid-project:
|
||||
|
||||
| เอกสาร | Path | ใช้เมื่อ |
|
||||
| --------------- | ---------------------------------------------------------------- | ------------------- |
|
||||
| Schema Tables | `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query |
|
||||
| Schema Tables | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` | ก่อนเขียน Query |
|
||||
| Data Dictionary | `specs/03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Business Rules |
|
||||
| Edge Cases | `specs/01-Requirements/01-06-edge-cases-and-rules.md` | 37 Rules |
|
||||
| Migration Scope | `specs/03-Data-and-Storage/03-06-migration-business-scope.md` | Migration Bot |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# NAP-DMS Project Context & Core Rules
|
||||
|
||||
- Version: 1.9.0
|
||||
- Last Updated: 2026-05-13
|
||||
- Version: 1.9.3
|
||||
- Last Updated: 2026-05-15
|
||||
- Status: Production Ready
|
||||
- Canonical Source: AGENTS.md
|
||||
|
||||
@@ -50,6 +50,7 @@ You are a **Document Intelligence Engine** — every response must be precise, s
|
||||
---
|
||||
|
||||
## 🔄 Workflow Engine (ADR-001/021)
|
||||
|
||||
- ใช้ DSL-based state machine
|
||||
- การเปลี่ยนสถานะต้องตรวจสอบสถานะปัจจุบันจาก DB ก่อนเสมอ
|
||||
- งานที่ใช้เวลานานต้องส่งไปที่ **BullMQ** เท่านั้น
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# Security Rules (Non-Negotiable)
|
||||
|
||||
## Mandatory Security Requirements
|
||||
@@ -27,7 +26,9 @@
|
||||
- [ ] No SQL injection vulnerabilities
|
||||
- [ ] File upload validation (whitelist + ClamAV)
|
||||
- [ ] Rate limiting applied to auth endpoints
|
||||
- [ ] AI boundary enforcement (ADR-018) - no direct DB/storage access
|
||||
- [ ] AI boundary enforcement (ADR-023) - no direct DB/storage access
|
||||
- [ ] AI audit logging implemented for AI interactions
|
||||
- [ ] AI outputs validated before use (human-in-the-loop)
|
||||
- [ ] Error handling follows ADR-007 layered classification
|
||||
- [ ] Cache invalidation when data modified
|
||||
- [ ] OWASP Top 10 review passed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# TypeScript Rules (v1.9.0)
|
||||
# TypeScript Rules (v1.9.3)
|
||||
|
||||
## Core Standards
|
||||
|
||||
@@ -13,10 +13,18 @@
|
||||
## File & Function Structure
|
||||
|
||||
- **File Headers** — every file MUST start with `// File: path/filename` on the first line.
|
||||
- Use **absolute path** from project root (e.g., `// File: backend/src/modules/correspondence/correspondence.service.ts`)
|
||||
- Do NOT use relative path (e.g., `// File: src/example.service.ts`)
|
||||
- **Change Log** — include `// Change Log` at the top of the file.
|
||||
- **Single Export** — export **only one main symbol** per file.
|
||||
- **Function Style** — avoid unnecessary blank lines inside functions.
|
||||
|
||||
## i18n Guidelines
|
||||
|
||||
- **No Hardcoded Text:** Use i18n keys for all user-facing text
|
||||
- **Reference:** `specs/05-Engineering-Guidelines/05-08-i18n-guidelines.md`
|
||||
- **Pattern:** Use `t('key.path')` from i18n hook instead of hardcoded strings
|
||||
|
||||
## Patterns
|
||||
|
||||
```typescript
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# Domain Terminology
|
||||
|
||||
## DMS Glossary
|
||||
@@ -26,10 +25,11 @@ Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > oth
|
||||
| Document | Path | Use When |
|
||||
| ----------------------- | ----------------------------------------------------------------- | ------------------------------- |
|
||||
| **Glossary** | `specs/00-overview/00-02-glossary.md` | Verify domain terminology |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | Before writing any query |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` | Before writing any query |
|
||||
| **Data Dictionary** | `specs/03-Data-and-Storage/03-01-data-dictionary.md` | Field meanings + business rules |
|
||||
| **Edge Cases** | `specs/01-Requirements/01-06-edge-cases-and-rules.md` | Prevent bugs in flows |
|
||||
| **ADR-019 UUID** | `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | UUID-related work |
|
||||
| **ADR-023 AI** | `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md` | AI integration work |
|
||||
| **Backend Guidelines** | `specs/05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns |
|
||||
| **Frontend Guidelines** | `specs/05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns |
|
||||
| **Testing Strategy** | `specs/05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals |
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
|
||||
# Forbidden Actions
|
||||
|
||||
## ❌ Never Do This
|
||||
|
||||
| ❌ Forbidden | ✅ Correct Approach |
|
||||
| ----------------------------------------------- | ----------------------------------------------- |
|
||||
| ----------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| SQL Triggers for business logic | NestJS Service methods |
|
||||
| `.env` files in production | `docker-compose.yml` environment section |
|
||||
| TypeORM migration files | Edit schema SQL directly (ADR-009) |
|
||||
| Inventing table/column names | Verify against `schema-02-tables.sql` |
|
||||
| Inventing table/column names | Verify against `lcbp3-v1.9.0-schema-02-tables.sql` |
|
||||
| `any` TypeScript type | Proper types / generics |
|
||||
| `console.log` in committed code | NestJS Logger (backend) / remove (frontend) |
|
||||
| `req: any` in controllers | `RequestWithUser` typed interface |
|
||||
| `parseInt()` on UUID values | Use UUID string directly (ADR-019) |
|
||||
| Exposing INT PK in API responses | UUIDv7 (ADR-019) |
|
||||
| AI accessing DB/storage directly | AI → DMS API → DB (ADR-018) |
|
||||
| AI accessing DB/storage directly | AI → DMS API → DB (ADR-023) |
|
||||
| Direct file operations bypassing StorageService | `StorageService` for all file moves |
|
||||
| Inline email/notification sending | BullMQ queue job |
|
||||
| Deploying without Release Gates | Complete `04-08-release-management-policy.md` |
|
||||
| AI direct cloud API calls | On-premises Ollama only (ADR-018) |
|
||||
| AI outputs without human validation | Human-in-the-loop validation required (ADR-020) |
|
||||
| AI direct cloud API calls | On-premises Ollama only (ADR-023) |
|
||||
| AI outputs without human validation | Human-in-the-loop validation required (ADR-023) |
|
||||
| n8n calling Ollama/Qdrant directly | n8n → DMS API → BullMQ → Ollama/Qdrant (ADR-023A) |
|
||||
| Qdrant query without projectPublicId filter | QdrantService.search(projectPublicId: string) required (ADR-023A) |
|
||||
|
||||
## Schema Changes (ADR-009)
|
||||
|
||||
- **NO TypeORM migrations** — edit SQL schema directly
|
||||
- Always check `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` before writing queries
|
||||
- Always check `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` before writing queries
|
||||
- Update Data Dictionary when changing fields
|
||||
|
||||
## UUID Handling
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# Backend Patterns (NestJS)
|
||||
|
||||
## Architecture
|
||||
@@ -41,7 +40,7 @@ class Contract extends UuidBaseEntity {
|
||||
@Column({ type: 'uuid' })
|
||||
publicId: string;
|
||||
|
||||
@PrimaryKey()
|
||||
@PrimaryGeneratedColumn()
|
||||
@Exclude()
|
||||
id: number;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# Development Flow
|
||||
|
||||
## 🔴 Critical Work — DB / API / Security / Workflow Engine
|
||||
@@ -7,7 +6,7 @@
|
||||
|
||||
1. **Glossary check** — verify domain terms in `00-02-glossary.md`
|
||||
2. **Read the spec** — select from Key Spec Files table
|
||||
3. **Check schema** — verify table/column in `schema-02-tables.sql`
|
||||
3. **Check schema** — verify table/column in `lcbp3-v1.9.0-schema-02-tables.sql`
|
||||
4. **Check data dictionary** — confirm field meanings + business rules
|
||||
5. **Scan edge cases** — `01-06-edge-cases-and-rules.md`
|
||||
6. **Check ADRs** — verify decisions align (ADR-009, ADR-018, ADR-019)
|
||||
@@ -32,10 +31,10 @@
|
||||
## Context-Aware Triggers
|
||||
|
||||
| Request | Files to Check | Expected Response |
|
||||
| -------------------- | ------------------------------------------------------- | --------------------------------------------------- |
|
||||
| "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard |
|
||||
| -------------------- | -------------------------------------------------------------------- | --------------------------------------------------- |
|
||||
| "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `lcbp3-v1.9.0-schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard |
|
||||
| "แก้ฟอร์ม frontend" | `05-03-frontend-guidelines.md`, `01-06-edge-cases.md` | RHF+Zod + TanStack Query + Thai comments |
|
||||
| "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `schema-02-tables.sql` | Edit SQL directly + update Data Dictionary + Entity |
|
||||
| "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `lcbp3-v1.9.0-schema-02-tables.sql` | Edit SQL directly + update Data Dictionary + Entity |
|
||||
| "ตรวจสอบ UUID" | `ADR-019`, `05-07-hybrid-uuid-implementation-plan.md` | UUIDv7 MariaDB native UUID + TransformInterceptor |
|
||||
| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | Edit SQL schema directly + n8n workflow |
|
||||
| "ตรวจสอบ permission" | `seed-permissions.sql`, `ADR-016` | CASL 4-Level RBAC matrix |
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
|
||||
# ADR-020 AI Integration Architecture
|
||||
# ADR-023/023A AI Integration Architecture
|
||||
|
||||
## CRITICAL RULES
|
||||
|
||||
- **ALWAYS** follow ADR-018 AI boundary policy (isolation on Admin Desktop)
|
||||
- **ALWAYS** use RFA-First approach for AI implementation
|
||||
- **ALWAYS** follow ADR-023 AI boundary policy (isolation on Admin Desktop)
|
||||
- **ALWAYS** use ADR-023A 2-model stack (gemma4:e4b Q8_0 + nomic-embed-text)
|
||||
- **ALWAYS** use BullMQ 2-queue (ai-realtime + ai-batch) for GPU overload prevention
|
||||
- **NEVER** allow AI direct database/storage access
|
||||
- **ALWAYS** implement human-in-the-loop validation
|
||||
- **NEVER** send sensitive data to cloud AI services
|
||||
- **ALWAYS** enforce Qdrant projectPublicId filter (compile-time enforcement)
|
||||
- **NEVER** allow n8n to call Ollama/Qdrant directly (must go through DMS API → BullMQ)
|
||||
|
||||
## AI Integration Patterns
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
```
|
||||
Frontend → AI Gateway API → Admin Desktop (Ollama) → Backend Validation
|
||||
Frontend → AI Gateway API → BullMQ → Admin Desktop (Ollama) → Backend Validation
|
||||
n8n (Migration) → DMS API → BullMQ → Admin Desktop (Ollama) → Backend Validation
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| ----------------- | ------------------------- | ------------------------------------------------------------------------ |
|
||||
| **AI Gateway** | Backend (NestJS) | API endpoints, validation, audit logging |
|
||||
| **Ollama Engine** | Admin Desktop (Desk-5439) | LLM inference (Gemma 4) |
|
||||
| **OCR Engine** | Admin Desktop (Desk-5439) | Thai/English text extraction |
|
||||
| **Orchestrator** | QNAP NAS (n8n) | Workflow management |
|
||||
| **BullMQ Queues** | Backend (NestJS) | ai-realtime (RAG/Suggest), ai-batch (OCR/Extract/Embed) |
|
||||
| **Ollama Engine** | Admin Desktop (Desk-5439) | gemma4:e4b Q8_0 (LLM) + nomic-embed-text (Embedding) |
|
||||
| **OCR Engine** | Admin Desktop (Desk-5439) | PaddleOCR + PyThaiNLP (Thai/English text extraction) |
|
||||
| **Orchestrator** | QNAP NAS (n8n) | Migration Phase orchestrator only (calls DMS API, never Ollama directly) |
|
||||
|
||||
## Backend Implementation (NestJS)
|
||||
|
||||
@@ -32,24 +36,50 @@ Frontend → AI Gateway API → Admin Desktop (Ollama) → Backend Validation
|
||||
// AI Module with boundary enforcement
|
||||
@Module({
|
||||
controllers: [AiController],
|
||||
providers: [AiService, AiGateway],
|
||||
providers: [AiService, AiGateway, QdrantService],
|
||||
exports: [AiService],
|
||||
})
|
||||
export class AiModule {
|
||||
constructor() {
|
||||
// Enforce ADR-018 boundaries
|
||||
// Enforce ADR-023 boundaries
|
||||
}
|
||||
}
|
||||
|
||||
// QdrantService with compile-time projectPublicId enforcement
|
||||
@Injectable()
|
||||
export class QdrantService {
|
||||
async search(
|
||||
projectPublicId: string, // required — compile-time enforcement
|
||||
vector: number[],
|
||||
topK: number = 5,
|
||||
): Promise<QdrantSearchResult[]> {
|
||||
return this.client.search('documents', {
|
||||
vector,
|
||||
limit: topK,
|
||||
filter: {
|
||||
must: [{ key: 'project_public_id', match: { value: projectPublicId } }],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async upsert(
|
||||
projectPublicId: string, // required
|
||||
chunks: DocumentChunk[],
|
||||
): Promise<void> { ... }
|
||||
|
||||
// ❌ NEVER expose rawSearch() or method without projectPublicId filter
|
||||
}
|
||||
|
||||
// AI Service with validation
|
||||
@Injectable()
|
||||
export class AiService {
|
||||
async extractMetadata(documentId: string): Promise<AIMetadata> {
|
||||
// 1. Validate permissions
|
||||
// 2. Send to Admin Desktop AI
|
||||
// 3. Validate AI response
|
||||
// 4. Log audit trail
|
||||
// 5. Return validated results
|
||||
// 2. Queue job to BullMQ (ai-batch or ai-realtime)
|
||||
// 3. Worker sends to Admin Desktop AI (gemma4:e4b Q8_0)
|
||||
// 4. Validate AI response
|
||||
// 5. Log audit trail to ai_audit_logs
|
||||
// 6. Return validated results
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -74,24 +104,37 @@ const DocumentReviewForm = ({ document, aiSuggestions }) => {
|
||||
|
||||
## Security Requirements
|
||||
|
||||
- **AI Isolation:** All AI processing on Admin Desktop only
|
||||
- **AI Isolation:** All AI processing on Admin Desktop only (Desk-5439)
|
||||
- **Data Privacy:** No cloud AI services, on-premises only
|
||||
- **Audit Trail:** Log all AI interactions and human validations
|
||||
- **Audit Trail:** Log all AI interactions and human validations to ai_audit_logs
|
||||
- **Rate Limiting:** Prevent AI abuse and resource exhaustion
|
||||
- **Validation:** All AI outputs must be validated before use
|
||||
- **Multi-tenant Isolation:** Qdrant queries MUST include projectPublicId filter (compile-time enforcement)
|
||||
- **n8n Boundary:** n8n MUST call DMS API → BullMQ, NEVER Ollama/Qdrant directly
|
||||
- **GPU Overload Prevention:** BullMQ 2-queue (ai-realtime + ai-batch) with concurrency=1
|
||||
|
||||
## ADR-023A Specific Rules
|
||||
|
||||
- **2-Model Stack:** gemma4:e4b Q8_0 (~4.0GB) + nomic-embed-text (~0.3GB) = ~4.3GB VRAM peak
|
||||
- **PDF 3-Page Limit:** Classification/Tagging uses first 3 pages only (NOT RAG embedding)
|
||||
- **RAG Embedding:** Full document chunked at 512 tokens/64 tokens overlap
|
||||
- **OCR Auto-Detect:** PyMuPDF chars > 100 → Fast path, else PaddleOCR
|
||||
- **Embed Auto-Trigger:** AUTO after commit (parallel), gap covered by DB search
|
||||
- **Threshold Recalibration:** After 100-500 docs, based on ai_audit_logs analysis
|
||||
|
||||
## Required Implementation
|
||||
|
||||
- [ ] AiModule with ADR-018 boundary enforcement
|
||||
- [ ] AiModule with ADR-023 boundary enforcement
|
||||
- [ ] AI Gateway API endpoints with validation
|
||||
- [ ] BullMQ 2-queue setup (ai-realtime + ai-batch)
|
||||
- [ ] QdrantService with projectPublicId enforcement
|
||||
- [ ] DocumentReviewForm reusable component
|
||||
- [ ] Admin Desktop Ollama + PaddleOCR setup
|
||||
- [ ] n8n workflow orchestration
|
||||
- [ ] AI audit logging and monitoring
|
||||
- [ ] Admin Desktop Ollama (gemma4:e4b Q8_0 + nomic-embed-text) + PaddleOCR setup
|
||||
- [ ] n8n workflow orchestration (Migration Phase only)
|
||||
- [ ] AI audit logging and monitoring (ai_audit_logs)
|
||||
- [ ] Human-in-the-loop validation workflows
|
||||
|
||||
## Related Documents
|
||||
|
||||
- `specs/06-Decision-Records/ADR-018-ai-boundary.md`
|
||||
- `specs/06-Decision-Records/ADR-020-ai-intelligence-integration.md`
|
||||
- `specs/06-Decision-Records/ADR-017-ollama-data-migration.md`
|
||||
- `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md` (Base architecture)
|
||||
- `specs/06-Decision-Records/ADR-023A-unified-ai-architecture.md` (Model revision - current)
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# LCBP3 Agent Rules
|
||||
|
||||
Critical rules and guidelines for AI agents working on LCBP3-DMS.
|
||||
|
||||
## Version
|
||||
|
||||
- **Current:** v1.9.3
|
||||
- **Last Updated:** 2026-05-15
|
||||
- **Synced with:** `AGENTS.md` (v1.9.3)
|
||||
|
||||
## Purpose
|
||||
|
||||
This directory contains rule files that define:
|
||||
- Project context and role expectations
|
||||
- Critical Tier 1 rules (CI blockers)
|
||||
- Coding standards and patterns
|
||||
- Domain terminology and glossary
|
||||
- Development workflows
|
||||
- Security requirements
|
||||
- AI integration architecture (ADR-023/023A)
|
||||
|
||||
## Rule Enforcement Tiers
|
||||
|
||||
### 🔴 Tier 1 — CRITICAL (CI BLOCKER)
|
||||
|
||||
Build fails immediately if violated:
|
||||
- Security (Auth, RBAC, Validation)
|
||||
- UUID Strategy (ADR-019) — no `parseInt` / `Number` / `+` on UUID
|
||||
- Database correctness — verify schema before writing queries
|
||||
- File upload security (ClamAV + whitelist)
|
||||
- AI validation boundary (ADR-023)
|
||||
- Error handling strategy (ADR-007)
|
||||
- Forbidden patterns: `any`, `console.log`, UUID misuse, `id ?? ''` fallback
|
||||
|
||||
### 🟡 Tier 2 — IMPORTANT (CODE REVIEW)
|
||||
|
||||
Must fix before merge:
|
||||
- Architecture patterns (thin controller, business logic in service)
|
||||
- Test coverage (80%+ business logic, 70%+ backend overall)
|
||||
- Cache invalidation
|
||||
- Naming conventions
|
||||
- TypeScript Standards: Missing JSDoc, explicit types, or file headers
|
||||
|
||||
### 🟢 Tier 3 — GUIDELINES
|
||||
|
||||
Best practice — follow when possible:
|
||||
- Code style / formatting (Prettier handles)
|
||||
- Comment completeness
|
||||
- Minor optimizations
|
||||
|
||||
## Rule Files
|
||||
|
||||
### Core Rules (Tier 1 - CRITICAL)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `00-project-context.md` | Project context, role & persona, tier classification, specs folder organization |
|
||||
| `01-adr-019-uuid.md` | UUID handling strategy — no parseInt, use publicId only |
|
||||
| `02-security.md` | Security requirements, checklist, ADR-023/023A AI boundaries |
|
||||
|
||||
### Coding Standards
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `03-typescript.md` | TypeScript rules, file headers, i18n guidelines |
|
||||
| `06-backend-patterns.md` | NestJS patterns, UUID resolution, API response patterns |
|
||||
| `07-frontend-patterns.md` | Next.js patterns, RHF+Zod+TanStack Query, UUID handling |
|
||||
|
||||
### Domain & Workflow
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `04-domain-terminology.md` | DMS glossary, key spec files priority table |
|
||||
| `08-development-flow.md` | Development workflow by work type (Critical/Normal/Quick Fix) |
|
||||
|
||||
### Compliance & Architecture
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `05-forbidden-actions.md` | Actions that must never be done, schema changes, UUID handling |
|
||||
| `09-commit-checklist.md` | Pre-commit verification, commit message format |
|
||||
| `10-error-handling.md` | ADR-007 error handling strategy, layered classification |
|
||||
| `11-ai-integration.md` | ADR-023/023A AI architecture, 2-model stack, BullMQ 2-queue |
|
||||
|
||||
## Key Spec Files Priority
|
||||
|
||||
Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others
|
||||
|
||||
| Document | Path | Use When |
|
||||
|----------|------|----------|
|
||||
| **Glossary** | `specs/00-overview/00-02-glossary.md` | Verify domain terminology |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` | Before writing any query |
|
||||
| **Data Dictionary** | `specs/03-Data-and-Storage/03-01-data-dictionary.md` | Field meanings + business rules |
|
||||
| **Edge Cases** | `specs/01-Requirements/01-06-edge-cases-and-rules.md` | Prevent bugs in flows |
|
||||
| **ADR-019 UUID** | `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | UUID-related work |
|
||||
| **ADR-023 AI** | `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md` | AI integration work |
|
||||
| **Backend Guidelines** | `specs/05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns |
|
||||
| **Frontend Guidelines** | `specs/05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns |
|
||||
| **Testing Strategy** | `specs/05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals |
|
||||
|
||||
## Maintenance
|
||||
|
||||
When updating rules:
|
||||
|
||||
1. **Check AGENTS.md version** — Ensure rule files are synced
|
||||
2. **Update version numbers** — Bump version in `00-project-context.md` and `03-typescript.md`
|
||||
3. **Review ADR references** — Ensure all ADR references are current (ADR-023, ADR-023A, etc.)
|
||||
4. **Add new forbidden actions** — When new patterns are identified as violations
|
||||
5. **Update key spec files table** — When new ADRs or guidelines are added
|
||||
|
||||
## Related Documents
|
||||
|
||||
- `AGENTS.md` — Master agent configuration and context
|
||||
- `specs/06-Decision-Records/` — All Architecture Decision Records
|
||||
- `specs/05-Engineering-Guidelines/` — Backend, frontend, and testing guidelines
|
||||
@@ -1,6 +1,6 @@
|
||||
# `.agents/skills/` — LCBP3 Agent Skill Pack
|
||||
|
||||
**Version:** 1.8.9 | **Last Updated:** 2026-04-22 | **Total Skills:** 20
|
||||
**Version:** 1.9.0 | **Last Updated:** 2026-05-17 | **Total Skills:** 23
|
||||
|
||||
Agent skills for AI-assisted development in **Windsurf IDE** (and compatible agents: Codex CLI, opencode, Amp, Antigravity, AGENTS.md-aware tools).
|
||||
|
||||
@@ -16,12 +16,15 @@ Agent skills for AI-assisted development in **Windsurf IDE** (and compatible age
|
||||
├── README.md # (this file)
|
||||
├── nestjs-best-practices/ # Backend rules (40 rules across 10 categories)
|
||||
├── next-best-practices/ # Frontend rules (Next.js 15+)
|
||||
├── e2e-testing/ # Playwright E2E testing patterns (POM, flaky tests, CI/CD)
|
||||
├── verification-loop/ # Comprehensive verification (build, typecheck, lint, test, security)
|
||||
├── security-review/ # OWASP Top 10 + ADR compliance checklist
|
||||
└── speckit-*/ # 18 workflow skills (spec → plan → tasks → implement → …)
|
||||
```
|
||||
|
||||
Each skill directory contains:
|
||||
|
||||
- `SKILL.md` — frontmatter (`name`, `description`, `version: 1.8.9`, `scope`, `depends-on`, `handoffs`) + instructions
|
||||
- `SKILL.md` — frontmatter (`name`, `description`, `version: 1.9.0`, `scope`, `depends-on`, `handoffs`) + instructions
|
||||
- `templates/` _(optional)_ — artifact templates (spec/plan/tasks/checklist)
|
||||
- `rules/` _(nestjs only)_ — individual rule files grouped by prefix (`arch-`, `security-`, `db-`, etc.)
|
||||
|
||||
@@ -63,7 +66,7 @@ Use `/00-speckit.all` to run specify → clarify → plan → tasks → analyze
|
||||
From repo root:
|
||||
|
||||
| Script | Purpose |
|
||||
| --- | --- |
|
||||
| --------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| `./.agents/scripts/bash/check-prerequisites.sh --json` | Emit `FEATURE_DIR` + `AVAILABLE_DOCS` for a feature branch |
|
||||
| `./.agents/scripts/bash/setup-plan.sh --json` | Emit `FEATURE_SPEC`, `IMPL_PLAN`, `SPECS_DIR`, `BRANCH` |
|
||||
| `./.agents/scripts/bash/update-agent-context.sh windsurf` | Append tech entries to `AGENTS.md` |
|
||||
@@ -92,7 +95,7 @@ See [`_LCBP3-CONTEXT.md`](./_LCBP3-CONTEXT.md) for the complete list.
|
||||
|
||||
To add a new skill:
|
||||
|
||||
1. Create `NAME/SKILL.md` with frontmatter: `name`, `description`, `version: 1.8.9`, `scope`, `depends-on`.
|
||||
1. Create `NAME/SKILL.md` with frontmatter: `name`, `description`, `version: 1.9.0`, `scope`, `depends-on`.
|
||||
2. Append an LCBP3 context reference pointing to `_LCBP3-CONTEXT.md`.
|
||||
3. Wrap with `.windsurf/workflows/NAME.md` so it becomes a slash command.
|
||||
4. Update [`skills.md`](./skills.md) dependency matrix.
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
## 🔴 Tier 1 Non-Negotiables
|
||||
|
||||
- **ADR-019 UUID:** `publicId: string` exposed directly — **no** `@Expose({ name: 'id' })` rename; **no** `parseInt`/`Number`/`+` on UUID; **no** `id ?? ''` fallback in frontend.
|
||||
- **ADR-009:** No TypeORM migrations — edit `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` or add a `deltas/*.sql` file.
|
||||
- **ADR-009:** No TypeORM migrations — edit `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` or add a `deltas/*.sql` file.
|
||||
- **ADR-016 Security:** JWT + CASL 4-Level RBAC; `@UseGuards(JwtAuthGuard, CaslAbilityGuard)` on every mutation controller; `ThrottlerGuard` on auth; bcrypt 12 rounds; `Idempotency-Key` required on POST/PUT/PATCH.
|
||||
- **ADR-002 Document Numbering:** Redis Redlock + TypeORM `@VersionColumn` (double-lock). Never use application-side counter alone.
|
||||
- **ADR-008 Notifications:** BullMQ queue — never inline email/notification in a request thread.
|
||||
@@ -59,7 +59,7 @@
|
||||
| A plan | `.agents/skills/speckit-plan/templates/plan-template.md` + relevant ADRs |
|
||||
| Task breakdown | `.agents/skills/speckit-tasks/templates/tasks-template.md` + existing patterns in `specs/08-Tasks/` |
|
||||
| Acceptance criteria / UAT | `specs/01-Requirements/01-05-acceptance-criteria.md` |
|
||||
| Schema / table definition | `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` + `03-01-data-dictionary.md` |
|
||||
| Schema / table definition | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` + `03-01-data-dictionary.md` |
|
||||
| RBAC / permissions | `specs/03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` + `01-02-01-rbac-matrix.md` |
|
||||
| Release / hotfix | `specs/04-Infrastructure-OPS/04-08-release-management-policy.md` |
|
||||
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
---
|
||||
name: e2e-testing
|
||||
description: Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies for LCBP3-DMS.
|
||||
version: 1.9.0
|
||||
scope: testing
|
||||
depends-on: []
|
||||
handoffs-to: [speckit-tester]
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# E2E Testing Skill
|
||||
|
||||
Playwright E2E testing patterns adapted for LCBP3-DMS (NestJS + Next.js + MariaDB stack).
|
||||
|
||||
## LCBP3 Context
|
||||
|
||||
See [`_LCBP3-CONTEXT.md`](../_LCBP3-CONTEXT.md) for project-specific testing requirements:
|
||||
- Backend: Jest (Unit + Integration + E2E)
|
||||
- Frontend: Vitest (Unit) + Playwright (E2E)
|
||||
- E2E test location: `frontend/e2e/workflow-adr021.spec.ts`
|
||||
- Coverage goals: Backend 70%+, Business Logic 80%+
|
||||
|
||||
## When to Use
|
||||
|
||||
Invoke this skill when:
|
||||
- Creating new E2E tests for frontend features
|
||||
- Debugging flaky Playwright tests
|
||||
- Setting up CI/CD integration for E2E tests
|
||||
- Optimizing test performance and reliability
|
||||
- Implementing Page Object Model (POM) patterns
|
||||
|
||||
## Test File Organization
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── e2e/
|
||||
│ ├── auth/
|
||||
│ │ ├── login.spec.ts
|
||||
│ │ └── logout.spec.ts
|
||||
│ ├── correspondence/
|
||||
│ │ ├── create.spec.ts
|
||||
│ │ └── workflow.spec.ts
|
||||
│ ├── transmittals/
|
||||
│ │ ├── create.spec.ts
|
||||
│ │ └── submit.spec.ts
|
||||
│ ├── circulation/
|
||||
│ │ ├── routing.spec.ts
|
||||
│ │ └── approval.spec.ts
|
||||
│ └── workflow-adr021.spec.ts # Existing ADR-021 integration test
|
||||
├── playwright.config.ts
|
||||
└── tests/
|
||||
└── fixtures/
|
||||
├── auth.ts
|
||||
└── data.ts
|
||||
```
|
||||
|
||||
## Page Object Model (POM)
|
||||
|
||||
```typescript
|
||||
// frontend/e2e/pages/CorrespondencePage.ts
|
||||
import { Page, Locator } from '@playwright/test'
|
||||
|
||||
export class CorrespondencePage {
|
||||
readonly page: Page
|
||||
readonly createButton: Locator
|
||||
readonly subjectInput: Locator
|
||||
readonly recipientSelect: Locator
|
||||
readonly submitButton: Locator
|
||||
readonly successMessage: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.createButton = page.getByTestId('create-correspondence')
|
||||
this.subjectInput = page.getByTestId('subject-input')
|
||||
this.recipientSelect = page.getByTestId('recipient-select')
|
||||
this.submitButton = page.getByTestId('submit-button')
|
||||
this.successMessage = page.getByTestId('success-message')
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/admin/doc-control/correspondences')
|
||||
await this.page.waitForLoadState('networkidle')
|
||||
}
|
||||
|
||||
async createCorrespondence(data: {
|
||||
subject: string
|
||||
recipientId: string
|
||||
}) {
|
||||
await this.createButton.click()
|
||||
await this.subjectInput.fill(data.subject)
|
||||
await this.recipientSelect.selectOption(data.recipientId)
|
||||
await this.submitButton.click()
|
||||
}
|
||||
|
||||
async verifySuccess() {
|
||||
await expect(this.successMessage).toBeVisible()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
```typescript
|
||||
// frontend/e2e/correspondence/create.spec.ts
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { CorrespondencePage } from '../pages/CorrespondencePage'
|
||||
|
||||
test.describe('Correspondence Creation', () => {
|
||||
let correspondencePage: CorrespondencePage
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
correspondencePage = new CorrespondencePage(page)
|
||||
await correspondencePage.goto()
|
||||
})
|
||||
|
||||
test('should create correspondence successfully', async ({ page }) => {
|
||||
await correspondencePage.createCorrespondence({
|
||||
subject: 'Test Correspondence',
|
||||
recipientId: 'test-recipient-id'
|
||||
})
|
||||
|
||||
await correspondencePage.verifySuccess()
|
||||
await page.screenshot({ path: 'artifacts/correspondence-created.png' })
|
||||
})
|
||||
|
||||
test('should validate required fields', async ({ page }) => {
|
||||
await correspondencePage.createButton.click()
|
||||
await correspondencePage.submitButton.click()
|
||||
|
||||
await expect(page.getByTestId('subject-error')).toBeVisible()
|
||||
await expect(page.getByTestId('recipient-error')).toBeVisible()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Playwright Configuration
|
||||
|
||||
```typescript
|
||||
// frontend/playwright.config.ts
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: [
|
||||
['html', { outputFolder: 'playwright-report' }],
|
||||
['junit', { outputFile: 'playwright-results.xml' }],
|
||||
['json', { outputFile: 'playwright-results.json' }]
|
||||
],
|
||||
use: {
|
||||
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
actionTimeout: 10000,
|
||||
navigationTimeout: 30000,
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||
],
|
||||
webServer: {
|
||||
command: 'pnpm dev',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120000,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Flaky Test Patterns
|
||||
|
||||
### Quarantine
|
||||
|
||||
```typescript
|
||||
test('flaky: complex workflow', async ({ page }) => {
|
||||
test.fixme(true, 'Flaky - Issue #123')
|
||||
// test code...
|
||||
})
|
||||
|
||||
test('conditional skip', async ({ page }) => {
|
||||
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
|
||||
// test code...
|
||||
})
|
||||
```
|
||||
|
||||
### Identify Flakiness
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npx playwright test e2e/correspondence/create.spec.ts --repeat-each=10
|
||||
npx playwright test e2e/correspondence/create.spec.ts --retries=3
|
||||
```
|
||||
|
||||
### Common Causes & Fixes
|
||||
|
||||
**Race conditions:**
|
||||
```typescript
|
||||
// Bad: assumes element is ready
|
||||
await page.click('[data-testid="submit-button"]')
|
||||
|
||||
// Good: auto-wait locator
|
||||
await page.locator('[data-testid="submit-button"]').click()
|
||||
```
|
||||
|
||||
**Network timing:**
|
||||
```typescript
|
||||
// Bad: arbitrary timeout
|
||||
await page.waitForTimeout(5000)
|
||||
|
||||
// Good: wait for specific condition
|
||||
await page.waitForResponse(resp =>
|
||||
resp.url().includes('/api/correspondences') && resp.status() === 201
|
||||
)
|
||||
```
|
||||
|
||||
**Animation timing:**
|
||||
```typescript
|
||||
// Bad: click during animation
|
||||
await page.click('[data-testid="menu-item"]')
|
||||
|
||||
// Good: wait for stability
|
||||
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.locator('[data-testid="menu-item"]').click()
|
||||
```
|
||||
|
||||
## Artifact Management
|
||||
|
||||
### Screenshots
|
||||
|
||||
```typescript
|
||||
await page.screenshot({ path: 'artifacts/after-login.png' })
|
||||
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
|
||||
await page.locator('[data-testid="workflow-banner"]').screenshot({
|
||||
path: 'artifacts/workflow-banner.png'
|
||||
})
|
||||
```
|
||||
|
||||
### Traces
|
||||
|
||||
```typescript
|
||||
// In playwright.config.ts
|
||||
use: {
|
||||
trace: 'on-first-retry'
|
||||
}
|
||||
|
||||
// View trace
|
||||
npx playwright show-trace trace.zip
|
||||
```
|
||||
|
||||
### Video
|
||||
|
||||
```typescript
|
||||
// In playwright.config.ts
|
||||
use: {
|
||||
video: 'retain-on-failure',
|
||||
videosPath: 'artifacts/videos/'
|
||||
}
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
```yaml
|
||||
# .github/workflows/e2e.yml
|
||||
name: E2E Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: pnpm install
|
||||
- run: cd frontend && npx playwright install --with-deps
|
||||
- run: cd frontend && npx playwright test
|
||||
env:
|
||||
BASE_URL: ${{ vars.STAGING_URL }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: frontend/playwright-report/
|
||||
retention-days: 30
|
||||
```
|
||||
|
||||
## Test Report Template
|
||||
|
||||
```markdown
|
||||
# E2E Test Report
|
||||
|
||||
**Date:** YYYY-MM-DD HH:MM
|
||||
**Duration:** Xm Ys
|
||||
**Status:** PASSING / FAILING
|
||||
|
||||
## Summary
|
||||
- Total: X | Passed: Y (Z%) | Failed: A | Flaky: B | Skipped: C
|
||||
|
||||
## Failed Tests
|
||||
|
||||
### correspondence-create
|
||||
**File:** `frontend/e2e/correspondence/create.spec.ts:45`
|
||||
**Error:** Expected element to be visible
|
||||
**Screenshot:** artifacts/failed.png
|
||||
**Recommended Fix:** Add waitForLoadState after form submission
|
||||
|
||||
## Artifacts
|
||||
- HTML Report: frontend/playwright-report/index.html
|
||||
- Screenshots: frontend/artifacts/*.png
|
||||
- Videos: frontend/artifacts/videos/*.webm
|
||||
- Traces: frontend/artifacts/*.zip
|
||||
```
|
||||
|
||||
## Critical Flow Testing
|
||||
|
||||
```typescript
|
||||
// frontend/e2e/workflow/adr021.spec.ts
|
||||
test('workflow: correspondence → rfa → approval', async ({ page }) => {
|
||||
// Create correspondence
|
||||
await createCorrespondence(page)
|
||||
await expect(page.getByTestId('correspondence-created')).toBeVisible()
|
||||
|
||||
// Submit for RFA
|
||||
await page.getByTestId('submit-rfa').click()
|
||||
await expect(page.getByTestId('rfa-submitted')).toBeVisible()
|
||||
|
||||
// Approve RFA
|
||||
await page.goto('/admin/doc-control/rfa/123')
|
||||
await page.getByTestId('approve-button').click()
|
||||
await expect(page.getByTestId('approval-success')).toBeVisible()
|
||||
|
||||
// Verify workflow state
|
||||
await expect(page.getByTestId('workflow-state')).toContainText('APPROVED')
|
||||
})
|
||||
```
|
||||
|
||||
## LCBP3-Specific Considerations
|
||||
|
||||
- **UUID Handling:** Use `publicId` (string UUID) in E2E tests, never `parseInt()` (ADR-019)
|
||||
- **Authentication:** Mock auth tokens for E2E tests to avoid real auth flows
|
||||
- **Workflow States:** Test ADR-021 workflow transitions (DRAFT → PENDING → APPROVED)
|
||||
- **i18n:** Test with Thai language to verify i18n key resolution
|
||||
- **RBAC:** Test different user roles (admin, user, reviewer) for permission checks
|
||||
|
||||
## References
|
||||
|
||||
- LCBP3 Testing Strategy: `specs/05-Engineering-Guidelines/05-04-testing-strategy.md`
|
||||
- ADR-021 Workflow Context: `specs/06-Decision-Records/ADR-021-workflow-context.md`
|
||||
- Existing E2E test: `frontend/e2e/workflow-adr021.spec.ts`
|
||||
@@ -126,7 +126,7 @@ These rules override general NestJS best practices for the NAP-DMS project:
|
||||
### ADR-009: No TypeORM Migrations
|
||||
|
||||
- **ห้ามสร้างไฟล์ migration ของ TypeORM**
|
||||
- แก้ไข schema โดยตรงที่: `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql`
|
||||
- แก้ไข schema โดยตรงที่: `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql`
|
||||
- ใช้ n8n workflow สำหรับ data migration ถ้าจำเป็น
|
||||
|
||||
### ADR-019: Hybrid Identifier Strategy (CRITICAL — March 2026 Pattern)
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
---
|
||||
name: security-review
|
||||
description: Comprehensive security review for LCBP3-DMS with OWASP Top 10 checklist, ADR compliance, and automated security testing patterns.
|
||||
version: 1.9.0
|
||||
scope: security
|
||||
depends-on: []
|
||||
handoffs-to: [speckit-reviewer, speckit-security-audit]
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Security Review Skill
|
||||
|
||||
Comprehensive security review for LCBP3-DMS ensuring all code follows security best practices and identifies potential vulnerabilities.
|
||||
|
||||
## LCBP3 Context
|
||||
|
||||
See [`_LCBP3-CONTEXT.md`](../_LCBP3-CONTEXT.md) for project-specific security requirements:
|
||||
- **ADR-016**: Security & Authentication (JWT, CASL, RBAC, file upload)
|
||||
- **ADR-018**: AI Boundary (Ollama on Admin Desktop only, no direct DB/storage access)
|
||||
- **ADR-019**: UUID Strategy (no parseInt/Number/+ on UUID)
|
||||
- **ADR-023**: Unified AI Architecture (AI via DMS API only)
|
||||
- **ADR-007**: Error Handling (layered error classification)
|
||||
|
||||
## When to Activate
|
||||
|
||||
Invoke this skill:
|
||||
- Implementing authentication or authorization
|
||||
- Handling user input or file uploads
|
||||
- Creating new API endpoints
|
||||
- Working with secrets or credentials
|
||||
- Integrating AI features (Ollama/Qdrant)
|
||||
- Storing or transmitting sensitive data
|
||||
- Integrating third-party APIs
|
||||
|
||||
## Security Checklist
|
||||
|
||||
### 1. Secrets Management
|
||||
|
||||
#### FAIL: NEVER Do This
|
||||
```typescript
|
||||
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
|
||||
const dbPassword = "password123" // In source code
|
||||
```
|
||||
|
||||
#### PASS: ALWAYS Do This
|
||||
```typescript
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
const dbUrl = process.env.DATABASE_URL
|
||||
|
||||
// Verify secrets exist
|
||||
if (!apiKey) {
|
||||
throw new Error('OPENAI_API_KEY not configured')
|
||||
}
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] No hardcoded API keys, tokens, or passwords
|
||||
- [ ] All secrets in environment variables
|
||||
- [ ] `.env.local` in .gitignore
|
||||
- [ ] No secrets in git history
|
||||
- [ ] Production secrets in QNAP docker-compose environment section (not .env files)
|
||||
|
||||
### 2. Input Validation
|
||||
|
||||
#### Always Validate User Input
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
// Define validation schema
|
||||
const CreateCorrespondenceSchema = z.object({
|
||||
subject: z.string().min(1).max(500),
|
||||
recipientId: z.string().uuid(),
|
||||
typeCode: z.string().min(1).max(50)
|
||||
})
|
||||
|
||||
// Validate before processing
|
||||
export async function createCorrespondence(input: unknown) {
|
||||
try {
|
||||
const validated = CreateCorrespondenceSchema.parse(input)
|
||||
return await correspondenceService.create(validated)
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
throw new BadRequestException(error.errors)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### File Upload Validation (ADR-016)
|
||||
```typescript
|
||||
function validateFileUpload(file: Express.Multer.File) {
|
||||
// Size check (50MB max per ADR-016)
|
||||
const maxSize = 50 * 1024 * 1024
|
||||
if (file.size > maxSize) {
|
||||
throw new BadRequestException('File too large (max 50MB)')
|
||||
}
|
||||
|
||||
// Type check (whitelist: PDF, DWG, DOCX, XLSX, ZIP)
|
||||
const allowedTypes = [
|
||||
'application/pdf',
|
||||
'application/vnd.dwg',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/zip'
|
||||
]
|
||||
if (!allowedTypes.includes(file.mimetype)) {
|
||||
throw new BadRequestException('Invalid file type')
|
||||
}
|
||||
|
||||
// Extension check
|
||||
const allowedExtensions = ['.pdf', '.dwg', '.docx', '.xlsx', '.zip']
|
||||
const extension = path.extname(file.originalname).toLowerCase()
|
||||
if (!allowedExtensions.includes(extension)) {
|
||||
throw new BadRequestException('Invalid file extension')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] All user inputs validated with Zod (frontend) + class-validator (backend)
|
||||
- [ ] File uploads restricted (50MB max, whitelist types)
|
||||
- [ ] No direct use of user input in queries
|
||||
- [ ] Whitelist validation (not blacklist)
|
||||
- [ ] Error messages don't leak sensitive info
|
||||
|
||||
### 3. SQL Injection Prevention
|
||||
|
||||
#### FAIL: NEVER Concatenate SQL
|
||||
```typescript
|
||||
// DANGEROUS - SQL Injection vulnerability
|
||||
const query = `SELECT * FROM correspondences WHERE uuid = '${correspondenceUuid}'`
|
||||
await this.connection.query(query)
|
||||
```
|
||||
|
||||
#### PASS: ALWAYS Use TypeORM Parameterized Queries
|
||||
```typescript
|
||||
// Safe - TypeORM parameterized query
|
||||
const correspondence = await this.correspondenceRepository.findOne({
|
||||
where: { publicId: correspondenceUuid }
|
||||
})
|
||||
|
||||
// Or with QueryBuilder
|
||||
const result = await this.correspondenceRepository
|
||||
.createQueryBuilder('c')
|
||||
.where('c.publicId = :uuid', { uuid: correspondenceUuid })
|
||||
.getOne()
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] All database queries use TypeORM parameterized queries
|
||||
- [ ] No string concatenation in SQL
|
||||
- [ ] TypeORM query builder used correctly
|
||||
- [ ] Schema verified before writing queries (ADR-009)
|
||||
|
||||
### 4. Authentication & Authorization (ADR-016)
|
||||
|
||||
#### JWT Token Handling
|
||||
```typescript
|
||||
// FAIL: WRONG: localStorage (vulnerable to XSS)
|
||||
localStorage.setItem('token', token)
|
||||
|
||||
// PASS: CORRECT: httpOnly cookies
|
||||
response.setHeader('Set-Cookie',
|
||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`
|
||||
)
|
||||
```
|
||||
|
||||
#### Authorization Checks (CASL)
|
||||
```typescript
|
||||
// Controller with CASL guard
|
||||
@Post()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard, AbilitiesGuard)
|
||||
@CheckAbilities({ action: 'create', subject: 'Correspondence' })
|
||||
async create(@Body() dto: CreateCorrespondenceDto, @Request() req) {
|
||||
// Service logic
|
||||
}
|
||||
```
|
||||
|
||||
#### RBAC Matrix (ADR-016)
|
||||
- [ ] 4-Level RBAC matrix implemented (Admin, Manager, User, Viewer)
|
||||
- [ ] CASL AbilityFactory configured with correct permissions
|
||||
- [ ] JwtAuthGuard on all protected routes
|
||||
- [ ] RolesGuard for role-based access
|
||||
- [ ] AuditLogInterceptor on all mutation endpoints
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] Tokens stored in httpOnly cookies (not localStorage)
|
||||
- [ ] Authorization checks before sensitive operations
|
||||
- [ ] CASL abilities configured correctly
|
||||
- [ ] Role-based access control implemented
|
||||
- [ ] Session management secure
|
||||
|
||||
### 5. XSS Prevention
|
||||
|
||||
#### Sanitize HTML
|
||||
```typescript
|
||||
import DOMPurify from 'isomorphic-dompurify'
|
||||
|
||||
// ALWAYS sanitize user-provided HTML
|
||||
function renderUserContent(html: string) {
|
||||
const clean = DOMPurify.sanitize(html, {
|
||||
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
|
||||
ALLOWED_ATTR: []
|
||||
})
|
||||
return <div dangerouslySetInnerHTML={{ __html: clean }} />
|
||||
}
|
||||
```
|
||||
|
||||
#### Content Security Policy (Next.js)
|
||||
```typescript
|
||||
// next.config.js
|
||||
const securityHeaders = [
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: `
|
||||
default-src 'self';
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' data: https:;
|
||||
font-src 'self';
|
||||
connect-src 'self' http://localhost:3001 https://192.168.10.8;
|
||||
`.replace(/\s{2,}/g, ' ').trim()
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] User-provided HTML sanitized
|
||||
- [ ] CSP headers configured
|
||||
- [ ] No unvalidated dynamic content rendering
|
||||
- [ ] React's built-in XSS protection used
|
||||
|
||||
### 6. CSRF Protection
|
||||
|
||||
#### CSRF Tokens
|
||||
```typescript
|
||||
import { csrf } from '@/lib/csrf'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const token = request.headers.get('X-CSRF-Token')
|
||||
|
||||
if (!csrf.verify(token)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid CSRF token' },
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
// Process request
|
||||
}
|
||||
```
|
||||
|
||||
#### SameSite Cookies
|
||||
```typescript
|
||||
response.setHeader('Set-Cookie',
|
||||
`session=${sessionId}; HttpOnly; Secure; SameSite=Strict`
|
||||
)
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] CSRF tokens on state-changing operations
|
||||
- [ ] SameSite=Strict on all cookies
|
||||
- [ ] Double-submit cookie pattern implemented
|
||||
|
||||
### 7. Rate Limiting (ADR-016)
|
||||
|
||||
#### API Rate Limiting
|
||||
```typescript
|
||||
import { ThrottlerGuard } from '@nestjs/throttler'
|
||||
|
||||
// Apply to auth endpoints
|
||||
@UseGuards(ThrottlerGuard)
|
||||
@Throttle({ default: { limit: 10, ttl: 60000 } })
|
||||
async login(@Body() dto: LoginDto) {
|
||||
// Login logic
|
||||
}
|
||||
```
|
||||
|
||||
#### Expensive Operations
|
||||
```typescript
|
||||
// Aggressive rate limiting for AI endpoints
|
||||
@Throttle({ default: { limit: 5, ttl: 60000 } })
|
||||
async extractMetadata(@Body() dto: ExtractMetadataDto) {
|
||||
// AI extraction logic
|
||||
}
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] Rate limiting on all auth endpoints (ADR-016)
|
||||
- [ ] Rate limiting on AI endpoints (ADR-018/023)
|
||||
- [ ] IP-based rate limiting
|
||||
- [ ] User-based rate limiting (authenticated)
|
||||
|
||||
### 8. Sensitive Data Exposure
|
||||
|
||||
#### Logging
|
||||
```typescript
|
||||
// FAIL: WRONG: Logging sensitive data
|
||||
this.logger.log('User login:', { email, password })
|
||||
this.logger.log('Payment:', { cardNumber, cvv })
|
||||
|
||||
// PASS: CORRECT: Redact sensitive data
|
||||
this.logger.log('User login:', { email, userId })
|
||||
this.logger.log('Payment:', { last4: card.last4, userId })
|
||||
```
|
||||
|
||||
#### Error Messages (ADR-007)
|
||||
```typescript
|
||||
// FAIL: WRONG: Exposing internal details
|
||||
catch (error) {
|
||||
return { error: error.message, stack: error.stack }
|
||||
}
|
||||
|
||||
// PASS: CORRECT: Generic error messages
|
||||
catch (error) {
|
||||
this.logger.error('Internal error:', error)
|
||||
throw new BadRequestException('An error occurred. Please try again.')
|
||||
}
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] No passwords, tokens, or secrets in logs
|
||||
- [ ] Error messages generic for users
|
||||
- [ ] Detailed errors only in server logs
|
||||
- [ ] No stack traces exposed to users
|
||||
|
||||
### 9. AI Boundary Enforcement (ADR-018/023)
|
||||
|
||||
#### FAIL: NEVER Do This
|
||||
```typescript
|
||||
// Direct AI access - FORBIDDEN
|
||||
import ollama from 'ollama'
|
||||
const response = await ollama.chat({ model: 'gemma4', messages })
|
||||
|
||||
// Direct Qdrant access - FORBIDDEN
|
||||
import { QdrantClient } from '@qdrant/js-client-rest'
|
||||
const client = new QdrantClient({ url: 'http://localhost:6333' })
|
||||
```
|
||||
|
||||
#### PASS: ALWAYS Do This
|
||||
```typescript
|
||||
// AI via DMS API only
|
||||
const response = await fetch('http://localhost:3001/api/ai/extract-metadata', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ documentId })
|
||||
})
|
||||
|
||||
// Qdrant via DMS API only
|
||||
const response = await fetch('http://localhost:3001/api/ai/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, projectPublicId })
|
||||
})
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] AI processing on Admin Desktop only (Desk-5439)
|
||||
- [ ] No direct Ollama calls from backend/frontend
|
||||
- [ ] No direct Qdrant calls from backend/frontend
|
||||
- [ ] All AI interactions via DMS API endpoints
|
||||
- [ ] AI audit logging implemented (ADR-020)
|
||||
- [ ] Human-in-the-loop validation for AI outputs
|
||||
|
||||
### 10. UUID Handling (ADR-019)
|
||||
|
||||
#### FAIL: NEVER Do This
|
||||
```typescript
|
||||
// parseInt on UUID - FORBIDDEN
|
||||
const projectId = parseInt(projectUuid) // "0195..." → 19 (WRONG!)
|
||||
|
||||
// Number on UUID - FORBIDDEN
|
||||
const projectId = Number(projectUuid)
|
||||
|
||||
// + operator on UUID - FORBIDDEN
|
||||
const projectId = +projectUuid
|
||||
|
||||
// id ?? '' fallback - FORBIDDEN
|
||||
const value = c.publicId ?? c.id ?? ''
|
||||
```
|
||||
|
||||
#### PASS: ALWAYS Do This
|
||||
```typescript
|
||||
// Use UUID string directly
|
||||
const projectId = projectUuid // "019505a1-7c3e-7000-8000-abc123def456"
|
||||
|
||||
// Backend: findOneByUuid returns entity with publicId
|
||||
const project = await this.projectService.findOneByUuid(projectUuid)
|
||||
const projectId = project.id // Internal INT for DB operations
|
||||
|
||||
// Frontend: use publicId only
|
||||
interface ProjectOption {
|
||||
publicId?: string; // No uuid fallback
|
||||
projectName?: string;
|
||||
}
|
||||
const value = c.publicId // "019505a1-7c3e-7000-8000-abc123def456"
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] No `parseInt()` on UUID values
|
||||
- [ ] No `Number()` on UUID values
|
||||
- [ ] No `+` operator on UUID values
|
||||
- [ ] No `id ?? ''` fallback patterns
|
||||
- [ ] Use `publicId` (string UUID) in API responses
|
||||
- [ ] Internal INT `id` marked with `@Exclude()` in entities
|
||||
|
||||
### 11. Dependency Security
|
||||
|
||||
#### Regular Updates
|
||||
```bash
|
||||
# Check for vulnerabilities
|
||||
pnpm audit
|
||||
|
||||
# Fix automatically fixable issues
|
||||
pnpm audit fix
|
||||
|
||||
# Update dependencies
|
||||
pnpm update
|
||||
|
||||
# Check for outdated packages
|
||||
pnpm outdated
|
||||
```
|
||||
|
||||
#### Lock Files
|
||||
```bash
|
||||
# ALWAYS commit lock files
|
||||
git add pnpm-lock.yaml
|
||||
|
||||
# Use in CI/CD for reproducible builds
|
||||
pnpm install --frozen-lockfile
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] Dependencies up to date
|
||||
- [ ] No known vulnerabilities (pnpm audit clean)
|
||||
- [ ] Lock files committed
|
||||
- [ ] Regular security updates
|
||||
|
||||
## Security Testing
|
||||
|
||||
### Automated Security Tests
|
||||
|
||||
```typescript
|
||||
// Test authentication
|
||||
test('requires authentication', async () => {
|
||||
const response = await fetch('/api/correspondences')
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
|
||||
// Test authorization
|
||||
test('requires admin role', async () => {
|
||||
const response = await fetch('/api/admin/users', {
|
||||
headers: { Authorization: `Bearer ${userToken}` }
|
||||
})
|
||||
expect(response.status).toBe(403)
|
||||
})
|
||||
|
||||
// Test input validation
|
||||
test('rejects invalid input', async () => {
|
||||
const response = await fetch('/api/correspondences', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ subject: '', recipientId: 'invalid' })
|
||||
})
|
||||
expect(response.status).toBe(400)
|
||||
})
|
||||
|
||||
// Test rate limiting
|
||||
test('enforces rate limits', async () => {
|
||||
const requests = Array(11).fill(null).map(() =>
|
||||
fetch('/api/auth/login', { method: 'POST' })
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
const tooManyRequests = responses.filter(r => r.status === 429)
|
||||
|
||||
expect(tooManyRequests.length).toBeGreaterThan(0)
|
||||
})
|
||||
```
|
||||
|
||||
## Pre-Deployment Security Checklist
|
||||
|
||||
Before ANY production deployment:
|
||||
|
||||
- [ ] **Secrets**: No hardcoded secrets, all in env vars
|
||||
- [ ] **Input Validation**: All user inputs validated (Zod + class-validator)
|
||||
- [ ] **SQL Injection**: All queries parameterized (TypeORM)
|
||||
- [ ] **XSS**: User content sanitized
|
||||
- [ ] **CSRF**: Protection enabled
|
||||
- [ ] **Authentication**: Proper token handling (httpOnly cookies)
|
||||
- [ ] **Authorization**: RBAC + CASL checks in place
|
||||
- [ ] **Rate Limiting**: Enabled on auth and AI endpoints
|
||||
- [ ] **HTTPS**: Enforced in production
|
||||
- [ ] **Security Headers**: CSP, X-Frame-Options configured
|
||||
- [ ] **Error Handling**: No sensitive data in errors (ADR-007)
|
||||
- [ ] **Logging**: No sensitive data logged
|
||||
- [ ] **Dependencies**: Up to date, no vulnerabilities
|
||||
- [ ] **UUID Handling**: No parseInt/Number/+ on UUID (ADR-019)
|
||||
- [ ] **AI Boundary**: AI via DMS API only (ADR-018/023)
|
||||
- [ ] **File Uploads**: Validated (50MB max, whitelist types)
|
||||
- [ ] **AI Audit**: All AI interactions logged (ADR-020)
|
||||
|
||||
## Resources
|
||||
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [NestJS Security](https://docs.nestjs.com/security)
|
||||
- [Next.js Security](https://nextjs.org/docs/security)
|
||||
- [ADR-016 Security Authentication](../../specs/06-Decision-Records/ADR-016-security-authentication.md)
|
||||
- [ADR-018 AI Boundary](../../specs/06-Decision-Records/ADR-018-ai-boundary.md)
|
||||
- [ADR-019 UUID Strategy](../../specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md)
|
||||
- [ADR-023 AI Architecture](../../specs/06-Decision-Records/ADR-023-unified-ai-architecture.md)
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Security is not optional. One vulnerability can compromise the entire platform. When in doubt, err on the side of caution.
|
||||
@@ -1,8 +1,8 @@
|
||||
# 🧠 NAP-DMS Agent Skills (v1.8.9)
|
||||
# 🧠 NAP-DMS Agent Skills (v1.9.0)
|
||||
|
||||
ไฟล์นี้กำหนดทักษะและความสามารถเฉพาะทางของ Document Intelligence Engine สำหรับโครงการ LCBP3 v1.8.9 เพื่อรักษามาตรฐานสูงสุดด้าน Security และ Data Integrity
|
||||
ไฟล์นี้กำหนดทักษะและความสามารถเฉพาะทางของ Document Intelligence Engine สำหรับโครงการ LCBP3 v1.9.0 เพื่อรักษามาตรฐานสูงสุดด้าน Security และ Data Integrity
|
||||
|
||||
**Status**: Production Ready | **Last Updated**: 2026-04-22 | **Total Skills**: 20
|
||||
**Status**: Production Ready | **Last Updated**: 2026-05-17 | **Total Skills**: 23
|
||||
|
||||
> 📌 Shared context for all speckit-\* skills: see [`_LCBP3-CONTEXT.md`](./_LCBP3-CONTEXT.md).
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
## 🔄 Skill Dependency Matrix
|
||||
|
||||
| Skill | Dependencies | Handoffs To | Notes |
|
||||
| -------------------------- | -------------------- | -------------------------------- | ----------------------------- |
|
||||
| -------------------------- | -------------------- | ---------------------------------------- | ----------------------------- |
|
||||
| **speckit-constitution** | None | speckit-specify | Project governance foundation |
|
||||
| **speckit-specify** | speckit-constitution | speckit-clarify | Feature specification |
|
||||
| **speckit-clarify** | speckit-specify | speckit-plan | Resolve ambiguities |
|
||||
@@ -79,6 +79,9 @@
|
||||
| **nestjs-best-practices** | None | speckit-implement | Backend patterns |
|
||||
| **next-best-practices** | None | speckit-implement | Frontend patterns |
|
||||
| **speckit-security-audit** | None | speckit-reviewer | Security validation |
|
||||
| **e2e-testing** | None | speckit-tester | Playwright E2E patterns |
|
||||
| **verification-loop** | None | speckit-checker, speckit-tester | Comprehensive verification |
|
||||
| **security-review** | None | speckit-reviewer, speckit-security-audit | OWASP Top 10 + ADR compliance |
|
||||
|
||||
---
|
||||
|
||||
@@ -96,8 +99,8 @@
|
||||
|
||||
### Health Metrics
|
||||
|
||||
- **Total Skills**: 20 implemented
|
||||
- **Version Alignment**: v1.8.9 across all skills
|
||||
- **Total Skills**: 23 implemented
|
||||
- **Version Alignment**: v1.9.0 across all skills
|
||||
- **Template Coverage**: 100% for skills requiring templates
|
||||
- **Documentation**: Complete front matter + shared `_LCBP3-CONTEXT.md` appendix
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
name: verification-loop
|
||||
description: A comprehensive verification system for LCBP3-DMS development sessions with build, type check, lint, test, security scan, and diff review phases.
|
||||
version: 1.9.0
|
||||
scope: verification
|
||||
depends-on: []
|
||||
handoffs-to: [speckit-checker, speckit-tester]
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Verification Loop Skill
|
||||
|
||||
A comprehensive verification system for LCBP3-DMS development sessions.
|
||||
|
||||
## LCBP3 Context
|
||||
|
||||
See [`_LCBP3-CONTEXT.md`](../_LCBP3-CONTEXT.md) for project-specific verification requirements:
|
||||
- Backend: NestJS with TypeScript strict mode
|
||||
- Frontend: Next.js with TypeScript strict mode
|
||||
- Package manager: pnpm
|
||||
- Coverage goals: Backend 70%+, Business Logic 80%+
|
||||
- Security: ADR-016, ADR-018, ADR-019, ADR-023 compliance
|
||||
|
||||
## When to Use
|
||||
|
||||
Invoke this skill:
|
||||
- After completing a feature or significant code change
|
||||
- Before creating a PR
|
||||
- When you want to ensure quality gates pass
|
||||
- After refactoring
|
||||
- Before deploying to staging/production
|
||||
|
||||
## Verification Phases
|
||||
|
||||
### Phase 1: Build Verification
|
||||
|
||||
```bash
|
||||
# Backend build
|
||||
cd backend
|
||||
pnpm build 2>&1 | tail -20
|
||||
|
||||
# Frontend build
|
||||
cd frontend
|
||||
pnpm build 2>&1 | tail -20
|
||||
```
|
||||
|
||||
If build fails, STOP and fix before continuing.
|
||||
|
||||
### Phase 2: Type Check
|
||||
|
||||
```bash
|
||||
# Backend TypeScript
|
||||
cd backend
|
||||
pnpm typecheck 2>&1 | head -30
|
||||
|
||||
# Frontend TypeScript
|
||||
cd frontend
|
||||
pnpm typecheck 2>&1 | head -30
|
||||
```
|
||||
|
||||
Report all type errors. Fix critical ones before continuing.
|
||||
|
||||
### Phase 3: Lint Check
|
||||
|
||||
```bash
|
||||
# Backend lint
|
||||
cd backend
|
||||
pnpm lint 2>&1 | head -30
|
||||
|
||||
# Frontend lint
|
||||
cd frontend
|
||||
pnpm lint 2>&1 | head -30
|
||||
```
|
||||
|
||||
### Phase 4: Test Suite
|
||||
|
||||
```bash
|
||||
# Backend tests with coverage
|
||||
cd backend
|
||||
pnpm test -- --coverage 2>&1 | tail -50
|
||||
|
||||
# Frontend unit tests
|
||||
cd frontend
|
||||
pnpm test 2>&1 | tail -50
|
||||
|
||||
# Frontend E2E tests (if applicable)
|
||||
cd frontend
|
||||
npx playwright test 2>&1 | tail -50
|
||||
```
|
||||
|
||||
Report:
|
||||
- Total tests: X
|
||||
- Passed: X
|
||||
- Failed: X
|
||||
- Coverage: X%
|
||||
|
||||
### Phase 5: Security Scan
|
||||
|
||||
```bash
|
||||
# Check for hardcoded secrets
|
||||
grep -rn "sk-" --include="*.ts" --include="*.tsx" . 2>/dev/null | head -10
|
||||
grep -rn "api_key" --include="*.ts" --include="*.tsx" . 2>/dev/null | head -10
|
||||
grep -rn "password" --include="*.ts" --include="*.tsx" . 2>/dev/null | head -10
|
||||
|
||||
# Check for console.log (forbidden in committed code)
|
||||
grep -rn "console.log" --include="*.ts" --include="*.tsx" backend/src/ frontend/src/ 2>/dev/null | head -10
|
||||
|
||||
# Check for any types (forbidden)
|
||||
grep -rn ": any" --include="*.ts" --include="*.tsx" backend/src/ frontend/src/ 2>/dev/null | head -10
|
||||
|
||||
# Check for parseInt on UUID (ADR-019 violation)
|
||||
grep -rn "parseInt(" --include="*.ts" --include="*.tsx" backend/src/ frontend/src/ 2>/dev/null | head -10
|
||||
```
|
||||
|
||||
### Phase 6: ADR Compliance Check
|
||||
|
||||
```bash
|
||||
# Check for id ?? '' fallback (ADR-019 violation)
|
||||
grep -rn "id ?? ''" --include="*.ts" --include="*.tsx" frontend/src/ 2>/dev/null | head -10
|
||||
|
||||
# Check for Number() on UUID (ADR-019 violation)
|
||||
grep -rn "Number(" --include="*.ts" --include="*.tsx" frontend/src/ 2>/dev/null | head -10
|
||||
|
||||
# Check for + operator on UUID (ADR-019 violation)
|
||||
grep -rn "+ publicId\|+ id" --include="*.ts" --include="*.tsx" frontend/src/ 2>/dev/null | head -10
|
||||
```
|
||||
|
||||
### Phase 7: Diff Review
|
||||
|
||||
```bash
|
||||
# Show what changed
|
||||
git diff --stat
|
||||
git diff HEAD~1 --name-only
|
||||
|
||||
# Show detailed changes
|
||||
git diff
|
||||
```
|
||||
|
||||
Review each changed file for:
|
||||
- Unintended changes
|
||||
- Missing error handling (ADR-007)
|
||||
- Potential edge cases
|
||||
- UUID handling (ADR-019)
|
||||
- Security vulnerabilities (ADR-016)
|
||||
- AI boundary violations (ADR-018/023)
|
||||
|
||||
## Output Format
|
||||
|
||||
After running all phases, produce a verification report:
|
||||
|
||||
```
|
||||
VERIFICATION REPORT
|
||||
==================
|
||||
|
||||
Build: [PASS/FAIL]
|
||||
Types: [PASS/FAIL] (X errors)
|
||||
Lint: [PASS/FAIL] (X warnings)
|
||||
Tests: [PASS/FAIL] (X/Y passed, Z% coverage)
|
||||
Security: [PASS/FAIL] (X issues)
|
||||
ADR: [PASS/FAIL] (X violations)
|
||||
Diff: [X files changed]
|
||||
|
||||
Overall: [READY/NOT READY] for PR
|
||||
|
||||
Issues to Fix:
|
||||
1. ...
|
||||
2. ...
|
||||
```
|
||||
|
||||
## Continuous Mode
|
||||
|
||||
For long sessions, run verification every 15 minutes or after major changes:
|
||||
|
||||
```markdown
|
||||
Set a mental checkpoint:
|
||||
- After completing each function
|
||||
- After finishing a component
|
||||
- Before moving to next task
|
||||
|
||||
Run: /verify
|
||||
```
|
||||
|
||||
## Integration with LCBP3 Skills
|
||||
|
||||
This skill complements:
|
||||
- **speckit-checker**: Runs static analysis (lint, typecheck)
|
||||
- **speckit-tester**: Runs tests with coverage verification
|
||||
- **speckit-security-audit**: Performs security review against OWASP Top 10
|
||||
|
||||
This skill provides a unified verification loop that combines all checks into a single report.
|
||||
|
||||
## LCBP3-Specific Checks
|
||||
|
||||
### Tier 1 — CRITICAL (CI BLOCKER)
|
||||
|
||||
- [ ] **Security**: Auth, RBAC, Validation implemented
|
||||
- [ ] **UUID Strategy (ADR-019)**: No `parseInt` / `Number` / `+` on UUID
|
||||
- [ ] **Database correctness**: Schema verified before writing queries
|
||||
- [ ] **File upload security**: ClamAV + whitelist implemented
|
||||
- [ ] **AI validation boundary (ADR-018/023)**: AI via DMS API only
|
||||
- [ ] **Error handling (ADR-007)**: Layered error classification
|
||||
- [ ] **Forbidden patterns**: Zero `any`, zero `console.log`, UUID misuse
|
||||
|
||||
### Tier 2 — IMPORTANT (CODE REVIEW)
|
||||
|
||||
- [ ] **Architecture patterns**: Thin controller, business logic in service
|
||||
- [ ] **Test coverage**: 80%+ business logic, 70%+ backend overall
|
||||
- [ ] **Cache invalidation**: Implemented when data modified
|
||||
- [ ] **Naming conventions**: Follow domain terminology
|
||||
|
||||
### Tier 3 — GUIDELINES
|
||||
|
||||
- [ ] **Code style**: Prettier formatting
|
||||
- [ ] **Comment completeness**: Thai comments, JSDoc on public methods
|
||||
- [ ] **Minor optimizations**: Performance improvements where applicable
|
||||
|
||||
## References
|
||||
|
||||
- LCBP3 AGENTS.md: `AGENTS.md` (repo root)
|
||||
- ADR-007 Error Handling: `specs/06-Decision-Records/ADR-007-error-handling-strategy.md`
|
||||
- ADR-016 Security: `specs/06-Decision-Records/ADR-016-security-authentication.md`
|
||||
- ADR-019 UUID: `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md`
|
||||
- ADR-018 AI Boundary: `specs/06-Decision-Records/ADR-018-ai-boundary.md`
|
||||
- ADR-023 AI Architecture: `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md`
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
auto_execution_mode: 0
|
||||
description: Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies for LCBP3-DMS
|
||||
---
|
||||
|
||||
This workflow invokes the e2e-testing skill to help with Playwright E2E testing patterns for LCBP3-DMS.
|
||||
|
||||
Invoke the e2e-testing skill when:
|
||||
- Creating new E2E tests for frontend features
|
||||
- Debugging flaky Playwright tests
|
||||
- Setting up CI/CD integration for E2E tests
|
||||
- Optimizing test performance and reliability
|
||||
- Implementing Page Object Model (POM) patterns
|
||||
@@ -40,7 +40,7 @@ The following are **CI-blocking issues** that must be caught in code review. The
|
||||
|
||||
- **❌ NO SQL Triggers for business logic** — use NestJS Service methods instead
|
||||
- **❌ NO `.env` files in production** — use Docker environment variables
|
||||
- **❌ NO direct table/column name invention** — verify against `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql`
|
||||
- **❌ NO direct table/column name invention** — verify against `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql`
|
||||
|
||||
### Security (ADR-016)
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
auto_execution_mode: 0
|
||||
description: Comprehensive security review for LCBP3-DMS with OWASP Top 10 checklist, ADR compliance, and automated security testing patterns
|
||||
---
|
||||
|
||||
This workflow invokes the security-review skill to perform comprehensive security review of LCBP3-DMS code changes.
|
||||
|
||||
Invoke the security-review skill when:
|
||||
- Implementing authentication or authorization
|
||||
- Handling user input or file uploads
|
||||
- Creating new API endpoints
|
||||
- Working with secrets or credentials
|
||||
- Integrating AI features (Ollama/Qdrant)
|
||||
- Storing or transmitting sensitive data
|
||||
- Integrating third-party APIs
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
auto_execution_mode: 0
|
||||
description: A comprehensive verification system for LCBP3-DMS development sessions with build, type check, lint, test, security scan, and diff review phases
|
||||
---
|
||||
|
||||
This workflow invokes the verification-loop skill to perform comprehensive verification of LCBP3-DMS code changes.
|
||||
|
||||
Invoke the verification-loop skill when:
|
||||
- After completing a feature or significant code change
|
||||
- Before creating a PR
|
||||
- When you want to ensure quality gates pass
|
||||
- After refactoring
|
||||
- Before deploying to staging/production
|
||||
+303
-40
@@ -1,76 +1,339 @@
|
||||
# NAP-DMS Gemini Rules & Standards
|
||||
|
||||
- For: Gemini 1.5 Pro / Flash / 2.0 (Google AI Studio, Vertex AI, Antigravity)
|
||||
- Version: 1.9.0 | Last synced from AGENTS.md: 2026-05-13
|
||||
- Project: LCBP3-DMS
|
||||
- Version: 1.9.3 | Last synced from AGENTS.md: 2026-05-16
|
||||
- Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3)
|
||||
- Skill pack: `.agents/skills/` (v1.9.0, 21 skills) — see [`skills/README.md`](../.agents/skills/README.md) + [`skills/_LCBP3-CONTEXT.md`](../.agents/skills/_LCBP3-CONTEXT.md)
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Role & Persona
|
||||
|
||||
Act as a **Senior Full Stack Developer** (NestJS, Next.js, TypeScript).
|
||||
You are a **Document Intelligence Engine** — focus on Data Integrity, Security, and Production-ready code.
|
||||
Act as a **Senior Full Stack Developer** specialized in:
|
||||
|
||||
- NestJS, Next.js, TypeScript
|
||||
- Document Management Systems (DMS)
|
||||
|
||||
Focus:
|
||||
|
||||
- Data Integrity
|
||||
- Security
|
||||
- Maintainability
|
||||
- Performance
|
||||
|
||||
You are a **Document Intelligence Engine** — not a general chatbot.
|
||||
Every response must be **precise**, **spec-compliant**, and **production-ready**.
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Thinking Protocol (Tier 1 & 2)
|
||||
## 🧩 Thought & Planning Protocol
|
||||
|
||||
Before any code changes:
|
||||
1. **Analyze**: Problem understanding + Context Search (Specs/ADRs) + Constraints.
|
||||
2. **Plan**: 2 Alternatives + Roadmap + Verification Plan (Tests).
|
||||
3. **Execute**: Follow roadmap + Summary of logic changes.
|
||||
Before writing any code or taking any action in Tier 1 and Tier 2, the AI must demonstrate the following thinking process:
|
||||
|
||||
### 1. Analysis Phase (Explore & Analyze)
|
||||
|
||||
Problem Understanding: Restate what the user wants in clear, unambiguous terms.
|
||||
Context Search: Identify the relevant Spec files or ADRs from the "Key Spec Files" table that must be read before starting.
|
||||
Constraints Identification: Identify key constraints (e.g. Security rules, UUID patterns, or Domain terminology).
|
||||
|
||||
### 2. Planning Phase (Plan)
|
||||
|
||||
Alternative Exploration: Present at least 2 solution approaches (where possible) with pros/cons analysis.
|
||||
Step-by-Step Roadmap: Write a file-by-file plan of changes before executing.
|
||||
Verification Plan: Specify how to verify the work is complete (e.g. "which unit tests to write" or "which file to check the schema in").
|
||||
|
||||
### 3. Execution & Refinement (Execute & Refine)
|
||||
|
||||
Follow the plan step by step, and pause to ask if any uncertainty arises.
|
||||
If significant logic changes are made, summarize what was done for the user after completion.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Tier 1 — CRITICAL (CI BLOCKER)
|
||||
## ⚙️ DMS Workflow Engine Protocol
|
||||
|
||||
1. **UUID (ADR-019):** No `parseInt` on UUID. Use `publicId` (string) only.
|
||||
2. **Database (ADR-009):** No TypeORM migrations. Edit `schema-02-tables.sql` directly.
|
||||
3. **Security (ADR-016):** CASL Guard for all APIs. Whitelist file uploads + ClamAV.
|
||||
4. **AI Boundary (ADR-018):** AI → DMS API → DB. No direct DB access.
|
||||
5. **Types:** ZERO `any`. ZERO `console.log`.
|
||||
กฎนี้ใช้คุมการเขียน Logic ส่วนการไหลของเอกสาร (RFA, Transmittal, Correspondence) เพื่อป้องกันปัญหา Race Condition และรักษาความถูกต้องของสถานะเอกสาร:
|
||||
|
||||
- **State Management:** ทุกการเปลี่ยนสถานะของ Workflow ต้องตรวจสอบสถานะปัจจุบันจากฐานข้อมูลก่อนเสมอ เพื่อป้องกันการอนุมัติซ้ำซ้อน
|
||||
- **Concurrency Control:** หากมีการเจนเลขที่เอกสาร (Document Numbering) ต้องใช้ **Redis Redlock** หรือ **TypeORM `@VersionColumn`** เท่านั้น ห้ามใช้ logic ฝั่งแอปพลิเคชันเพียงอย่างเดียว (ADR-002)
|
||||
- **Background Jobs:** งานที่ต้องใช้เวลานานหรือการแจ้งเตือน (Email/Notification) ต้องถูกส่งไปทำที่ **BullMQ** ห้ามเขียนแบบ Inline ใน Service (ADR-008)
|
||||
- **Term Consistency:** ห้ามใช้คำทั่วไปอย่าง "Approval Flow" ให้ใช้ **"Workflow Engine"** และห้ามใช้ "Letter" ให้ใช้ **"Correspondence"** ตามที่กำหนดใน Glossary
|
||||
|
||||
---
|
||||
|
||||
## 📐 TypeScript Standards (v1.9.0)
|
||||
## 🛡️ Security & Integrity Audit Protocol
|
||||
|
||||
- **File Header:** First line MUST be `// File: path/filename`.
|
||||
- **Change Log:** Include `// Change Log` at the top.
|
||||
- **Language:** Code in English, Comments/JSDoc in **Thai**.
|
||||
- **Compactness:** No blank lines inside functions.
|
||||
- **Export:** Single main symbol per file.
|
||||
- **Typing:** Explicit types for variables, parameters, and returns.
|
||||
กฎนี้จะช่วยให้ AI ทำหน้าที่เป็น Gatekeeper ก่อนที่คุณจะ Commit โค้ด โดยเน้นไปที่ **Tier 1 — CRITICAL**:
|
||||
|
||||
- **UUID Validation:** ทุกครั้งที่มีการรับค่า ID จาก API หรือ URL ต้องตรวจสอบว่าเป็น **UUIDv7** และห้ามใช้ `parseInt()` หรือตัวดำเนินการทางคณิตศาสตร์กับค่านี้เด็ดขาด (ADR-019)
|
||||
- **RBAC Check:** การสร้าง API ใหม่ต้องมี **CASL Guard** และตรวจสอบสิทธิ์แบบ 4-Level RBAC Matrix เสมอ (ADR-016)
|
||||
- **Data Isolation:** หากมีการใช้ฟีเจอร์ AI ต้องมั่นใจว่ารันผ่าน **Ollama บน Admin Desktop** เท่านั้น และห้ามให้ AI เข้าถึง Database หรือ Storage โดยตรง (ต้องผ่าน DMS API เท่านั้น) (ADR-023)
|
||||
- **Input Sanitization:** ไฟล์อัปโหลดต้องผ่านการตรวจสอบแบบ **Two-Phase** (Temp → Commit) และต้องสแกนด้วย **ClamAV** ก่อนย้ายเข้า Permanent Storage (ADR-016)
|
||||
|
||||
---
|
||||
|
||||
## 📁 Specs Organization (Hybrid Model)
|
||||
## 🧭 Rule Enforcement Tiers
|
||||
|
||||
- **Core (00-06):** Source of Truth (Overview, Req, Arch, Data, Ops, Guidelines, ADRs).
|
||||
- **Feature (100, 200, 300):** Implementation Work (100: Infra, 200: Fullstack, 300: Others).
|
||||
### 🔴 Tier 1 — CRITICAL (CI BLOCKER)
|
||||
|
||||
Build fails immediately if violated:
|
||||
|
||||
- Security (Auth, RBAC, Validation)
|
||||
- UUID Strategy (ADR-019) — no `parseInt` / `Number` / `+` on UUID
|
||||
- Database correctness — verify schema before writing queries
|
||||
- File upload security (ClamAV + whitelist)
|
||||
- AI validation boundary (ADR-023)
|
||||
- Error handling strategy (ADR-007)
|
||||
- Forbidden patterns: `any`, `console.log`, UUID misuse, `id ?? ''` fallback
|
||||
|
||||
### 🟡 Tier 2 — IMPORTANT (CODE REVIEW)
|
||||
|
||||
Must fix before merge:
|
||||
|
||||
- Architecture patterns (thin controller, business logic in service)
|
||||
- Test coverage (80%+ business logic, 70%+ backend overall)
|
||||
- Cache invalidation
|
||||
- Naming conventions
|
||||
- **TypeScript Standards:** Missing JSDoc, explicit types, or file headers
|
||||
|
||||
### 🟢 Tier 3 — GUIDELINES
|
||||
|
||||
Best practice — follow when possible:
|
||||
|
||||
- Code style / formatting (Prettier handles)
|
||||
- Comment completeness
|
||||
- Minor optimizations
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Security & Workflow (Non-Negotiable)
|
||||
## 🗂️ Key Spec Files (Always Check Before Writing Code)
|
||||
|
||||
- **Workflow (ADR-001/021):** DSL-based engine. Integrated context in UI.
|
||||
- **Numbering (ADR-002):** Redis Redlock + Optimistic Lock.
|
||||
- **Notifications (ADR-008):** BullMQ jobs (never inline).
|
||||
- **Error (ADR-007):** Layered classification + User-friendly messages.
|
||||
Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others
|
||||
|
||||
| Document | Path | Status | Use When |
|
||||
| ---------------------------- | -------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------- |
|
||||
| **Glossary** | `specs/00-overview/00-02-glossary.md` | — | Verify domain terminology |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` | — | Before writing any query |
|
||||
| **Data Dictionary** | `specs/03-Data-and-Storage/03-01-data-dictionary.md` | — | Field meanings + business rules |
|
||||
| **RBAC Matrix** | `specs/01-requirements/01-02-business-rules/01-02-01-rbac-matrix.md` | — | Permission levels + roles |
|
||||
| **Edge Cases** | `specs/01-Requirements/01-06-edge-cases-and-rules.md` | — | Prevent bugs in flows |
|
||||
| **ADR-001 Workflow Engine** | `specs/06-Decision-Records/ADR-001-unified-workflow-engine.md` | ✅ Active | DSL-based workflow implementation |
|
||||
| **ADR-002 Doc Numbering** | `specs/06-Decision-Records/ADR-002-document-numbering-strategy.md` | ✅ Active | Document number generation + locking |
|
||||
| **ADR-007 Error Handling** | `specs/06-Decision-Records/ADR-007-error-handling-strategy.md` | ✅ Active | Error patterns & recovery |
|
||||
| **ADR-008 Notifications** | `specs/06-Decision-Records/ADR-008-email-notification-strategy.md` | ✅ Active | BullMQ + multi-channel notification |
|
||||
| **ADR-009 DB Migration** | `specs/06-Decision-Records/ADR-009-database-migration-strategy.md` | ✅ Active | Schema changes — edit SQL directly |
|
||||
| **ADR-016 Security** | `specs/06-Decision-Records/ADR-016-security-authentication.md` | ✅ Active | Auth, RBAC, file upload security |
|
||||
| **ADR-019 UUID** | `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | ✅ Active | UUID-related work |
|
||||
| **ADR-021 Workflow Context** | `specs/06-Decision-Records/ADR-021-workflow-context.md` | ✅ Active | Integrated workflow & step attachments |
|
||||
| **ADR-023 AI Architecture** | `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md` | ✅ Active | Unified AI boundaries and pipeline (base architecture) |
|
||||
| **ADR-023A AI Model Rev.** | `specs/06-Decision-Records/ADR-023A-unified-ai-architecture.md` | ✅ Active | 2-Model stack (gemma4:e4b Q8_0), BullMQ 2-queue, RAG embed scope, OCR auto-detect |
|
||||
| **Backend Guidelines** | `specs/05-Engineering-Guidelines/05-02-backend-guidelines.md` | — | NestJS patterns |
|
||||
| **Frontend Guidelines** | `specs/05-Engineering-Guidelines/05-03-frontend-guidelines.md` | — | Next.js patterns |
|
||||
| **Testing Strategy** | `specs/05-Engineering-Guidelines/05-04-testing-strategy.md` | — | Coverage goals |
|
||||
| **Git Conventions** | `specs/05-Engineering-Guidelines/05-05-git-conventions.md` | — | Commit/branch naming |
|
||||
| **Code Snippets** | `specs/05-Engineering-Guidelines/05-06-code-snippets.md` | — | Reusable patterns |
|
||||
| **i18n Guidelines** | `specs/05-Engineering-Guidelines/05-08-i18n-guidelines.md` | — | Localization rules |
|
||||
| **Release Policy** | `specs/04-Infrastructure-OPS/04-08-release-management-policy.md` | — | Before deploy/hotfix |
|
||||
| **UAT Criteria** | `specs/01-Requirements/01-05-acceptance-criteria.md` | — | Feature completeness |
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Key References
|
||||
## 📁 Specs Folder Organization
|
||||
|
||||
| Area | Key File |
|
||||
| --- | --- |
|
||||
| Glossary | `specs/00-overview/00-02-glossary.md` |
|
||||
| Schema | `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` |
|
||||
| RBAC | `specs/01-requirements/01-02-business-rules/01-02-01-rbac-matrix.md` |
|
||||
| API | `specs/05-Engineering-Guidelines/05-02-backend-guidelines.md` |
|
||||
| UI | `specs/05-Engineering-Guidelines/05-03-frontend-guidelines.md` |
|
||||
โครงสร้างโฟลเดอร์ `specs/` แบ่งเป็น 2 ส่วนหลัก:
|
||||
|
||||
### 1. Core Specs (Permanent - ไม่เปลี่ยนชื่อ)
|
||||
|
||||
โฟลเดอร์เหล่านี้เป็น Source of Truth ถาวรของระบบ:
|
||||
|
||||
- `00-overview/` - ภาพรวมระบบ + Product Vision + KPI + Training
|
||||
- `01-requirements/` - Business Requirements & Modularity
|
||||
- `02-architecture/` - สถาปัตยกรรมระบบ (System & Network)
|
||||
- `03-Data-and-Storage/` - โครงสร้างฐานข้อมูลและการจัดการไฟล์
|
||||
- `04-Infrastructure-OPS/` - โครงสร้างพื้นฐานและการปฏิบัติการ
|
||||
- `05-Engineering-Guidelines/` - มาตรฐานการพัฒนาและการเขียนโค้ด
|
||||
- `06-Decision-Records/` - Architecture Decision Records (ADRs)
|
||||
- `08-Tasks/` - Task documents
|
||||
- `88-logs/` - Logs
|
||||
- `99-archives/` - ประวัติการทำงานและ Tasks เก่า
|
||||
|
||||
### 2. Feature Work (Categorized - ใช้สำหรับงาน Implement)
|
||||
|
||||
โฟลเดอร์เหล่านี้ใช้เก็บ plan.md, spec.md, tasks.md สำหรับงานที่กำลังดำเนินการ:
|
||||
|
||||
- `100-Infrastructures/` - งานที่เกี่ยวกับ Infrastructure (Deployment, Monitoring, Docker Compose, Network)
|
||||
- `200-fullstacks/` - งาน Fullstack Development (Backend + Frontend features, Workflow Engine, API)
|
||||
- `300-others/` - งานอื่นๆ (Documentation, Research, Non-code tasks)
|
||||
|
||||
---
|
||||
|
||||
## 🆔 Identifier Strategy (ADR-019) — CRITICAL
|
||||
|
||||
| Context | Type | Notes |
|
||||
| ---------------- | ------------------------- | ------------------------------------------- |
|
||||
| Internal / DB FK | `INT AUTO_INCREMENT` | Never exposed in API |
|
||||
| Public API / URL | `UUIDv7` (MariaDB native) | Stored as BINARY(16), no transformer needed |
|
||||
| Entity Property | `publicId: string` | Exposed directly in API (no transformation) |
|
||||
| API Response | `publicId: string` (UUID) | INT `id` has `@Exclude()` — never appears |
|
||||
|
||||
### ✅ Updated Pattern (March 2026)
|
||||
|
||||
**Backend:** `UuidBaseEntity` exposes `publicId` directly — no `@Expose({ name: 'id' })` transformation
|
||||
|
||||
**Frontend:** Use `publicId` only — no `uuid` or `id` fallbacks
|
||||
|
||||
### ❌ Forbidden UUID Patterns
|
||||
|
||||
```typescript
|
||||
// ❌ NEVER use parseInt on UUID
|
||||
parseInt(projectId); // "0195..." → 19 (WRONG!)
|
||||
|
||||
// ❌ NEVER use id ?? '' fallback
|
||||
const value = c.publicId ?? c.id ?? ''; // Wrong!
|
||||
|
||||
// ✅ CORRECT — Use publicId only
|
||||
const value = c.publicId; // "019505a1-7c3e-7000-8000-abc123def456"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Security Rules (Non-Negotiable)
|
||||
|
||||
1. **Idempotency:** All critical `POST`/`PUT`/`PATCH` MUST validate `Idempotency-Key` header
|
||||
2. **Two-Phase File Upload:** Upload → Temp → Commit → Permanent
|
||||
3. **Race Conditions:** Redis Redlock + TypeORM `@VersionColumn` for Document Numbering
|
||||
4. **Validation:** Zod (frontend) + class-validator (backend DTO)
|
||||
5. **Password:** bcrypt 12 salt rounds, min 8 chars, rotate every 90 days
|
||||
6. **Rate Limiting:** `ThrottlerGuard` on all auth endpoints
|
||||
7. **File Upload:** Whitelist PDF/DWG/DOCX/XLSX/ZIP, max 50MB, ClamAV scan
|
||||
8. **AI Isolation (ADR-023/023A):** Ollama on Admin Desktop ONLY — NO direct DB/storage access; 2-model stack `gemma4:e4b Q8_0` + `nomic-embed-text`; all inference via BullMQ (`ai-realtime` / `ai-batch`)
|
||||
9. **Error Handling (ADR-007):** Use layered error classification with user-friendly messages
|
||||
10. **AI Integration (ADR-023/023A):** RFA-First approach; n8n orchestrates Migration Phase only via DMS API — never calls Ollama directly; `QdrantService.search()` requires `projectPublicId` as mandatory param
|
||||
|
||||
---
|
||||
|
||||
## 📐 TypeScript Rules & Coding Standards
|
||||
|
||||
### 📝 Core Standards
|
||||
|
||||
- **Strict Mode** — all strict checks enforced.
|
||||
- **ZERO `any` types** — use proper types or `unknown` + narrowing.
|
||||
- **ZERO `console.log`** — use NestJS `Logger` (backend) or remove before commit (frontend).
|
||||
- **English for Code** — use English for all code identifiers, variables, and logic.
|
||||
- **Thai for Comments** — use Thai for comments, documentation, and JSDoc.
|
||||
- **Explicit Typing** — explicitly define types for all variables, parameters, and return values.
|
||||
- **JSDoc** — use JSDoc for all public classes and methods.
|
||||
|
||||
### 🏗️ File & Function Structure
|
||||
|
||||
- **File Headers** — every file MUST start with `// File: path/filename` on the first line.
|
||||
- **Change Log** — include `// Change Log` at the top of the file to track modifications.
|
||||
- **Single Export** — export **only one main symbol** (class, interface, or function) per file.
|
||||
- **Function Style** — avoid unnecessary blank lines inside functions to maintain compactness.
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Domain Terminology
|
||||
|
||||
| ✅ Use | ❌ Don't Use |
|
||||
| ------------------ | ------------------------------------- |
|
||||
| Correspondence | Letter, Communication, Document |
|
||||
| RFA | Approval Request, Submit for Approval |
|
||||
| Transmittal | Delivery Note, Cover Letter |
|
||||
| Circulation | Distribution, Routing |
|
||||
| Shop Drawing | Construction Drawing |
|
||||
| Contract Drawing | Design Drawing, Blueprint |
|
||||
| Workflow Engine | Approval Flow, Process Engine |
|
||||
| Document Numbering | Document ID, Auto Number |
|
||||
| RBAC | Permission System (generic) |
|
||||
|
||||
---
|
||||
|
||||
## 🚫 Forbidden Actions
|
||||
|
||||
| ❌ Forbidden | ✅ Correct Approach |
|
||||
| ----------------------------------------------- | ------------------------------------------------------- |
|
||||
| SQL Triggers for business logic | NestJS Service methods |
|
||||
| `.env` files in production | `docker-compose.yml` environment section |
|
||||
| TypeORM migration files | Edit schema SQL directly (ADR-009) |
|
||||
| Inventing table/column names | Verify against `lcbp3-v1.9.0-schema-02-tables.sql` |
|
||||
| `any` TypeScript type | Proper types / generics |
|
||||
| `console.log` in committed code | NestJS Logger (backend) / remove (frontend) |
|
||||
| `req: any` in controllers | `RequestWithUser` typed interface |
|
||||
| `parseInt()` on UUID values | Use UUID string directly (ADR-019) |
|
||||
| Exposing INT PK in API responses | UUIDv7 `publicId` (ADR-019) |
|
||||
| AI accessing DB/storage directly | AI → DMS API → DB (ADR-023/023A) |
|
||||
| Direct file operations bypassing StorageService | `StorageService` for all file moves |
|
||||
| Inline email/notification sending | BullMQ queue job (ADR-008) |
|
||||
| Deploying without Release Gates | Complete `04-08-release-management-policy.md` |
|
||||
| AI direct cloud API calls | On-premises Ollama only (ADR-023/023A) |
|
||||
| AI outputs without human validation | Human-in-the-loop validation required (ADR-023/023A) |
|
||||
| n8n calling Ollama/Qdrant directly | n8n → DMS API → BullMQ → Ollama (ADR-023A) |
|
||||
| Qdrant query without `projectPublicId` filter | `QdrantService.search(projectPublicId, ...)` (ADR-023A) |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Development Flow (Tiered)
|
||||
|
||||
### 🔴 Critical Work — DB / API / Security / Workflow Engine
|
||||
|
||||
**MUST complete all steps:**
|
||||
|
||||
1. **Glossary check** — verify domain terms in `00-02-glossary.md`
|
||||
2. **Read the spec** — select from Key Spec Files table
|
||||
3. **Check schema** — verify table/column in `lcbp3-v1.9.0-schema-02-tables.sql`
|
||||
4. **Check data dictionary** — confirm field meanings + business rules
|
||||
5. **Scan edge cases** — `01-06-edge-cases-and-rules.md`
|
||||
6. **Check ADRs** — verify decisions align (ADR-009, ADR-019, ADR-023)
|
||||
7. **Write code** — TypeScript strict, no `any`, no `console.log`, follow headers/JSDoc rules
|
||||
|
||||
### 🟡 Normal Work — UI / Feature / Integration
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Follow existing patterns in codebase
|
||||
2. Check spec for relevant module only
|
||||
3. Verify no forbidden patterns (`any`, `console.log`, UUID misuse)
|
||||
4. **Apply TypeScript Standards:** File headers, Thai comments, JSDoc
|
||||
|
||||
### 🟢 Quick Fix — Bug Fix / Typo / Style
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Identify root cause before changing code
|
||||
2. Apply minimal, targeted fix
|
||||
3. Add regression test if logic changed
|
||||
4. Verify no forbidden patterns introduced
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Context-Aware Triggers
|
||||
|
||||
When user asks about... check these files:
|
||||
|
||||
| Request | Files to Check | Expected Response |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||
| "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `lcbp3-v1.9.0-schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard |
|
||||
| "แก้ฟอร์ม frontend" | `05-03-frontend-guidelines.md`, `01-06-edge-cases-and-rules.md` | RHF+Zod + TanStack Query + Thai comments |
|
||||
| "เพิ่ม field ใหม่" | `ADR-009`, `03-01-data-dictionary.md`, `lcbp3-v1.9.0-schema-02-tables.sql` | Edit SQL directly + update Data Dictionary + Entity |
|
||||
| "ตรวจสอบ UUID" | `ADR-019`, `05-07-hybrid-uuid-implementation-plan.md` | UUIDv7 MariaDB native UUID + TransformInterceptor |
|
||||
| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | Edit SQL schema directly + n8n workflow |
|
||||
| "ตรวจสอบ permission" | `lcbp3-v1.9.0-seed-permissions.sql`, `ADR-016` | CASL 4-Level RBAC matrix |
|
||||
| "deploy production" | `04-08-release-management-policy.md`, `ADR-015` | Release Gates + Blue-Green strategy |
|
||||
| "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns |
|
||||
| "AI integration" | `ADR-023`, `ADR-023A` | AI boundary + 2-model stack + BullMQ queue policy |
|
||||
| "Error handling" | `ADR-007` | Layered error classification + recovery |
|
||||
| "File upload" | `ADR-016`, `05-02-backend-guidelines.md`, `03-Data-and-Storage/03-03-file-storage.md` | Two-phase upload → temp → commit; ClamAV + whitelist |
|
||||
| "Notifications / Queue" | `ADR-008`, `05-02-backend-guidelines.md` | BullMQ job — never inline; check retry + dead-letter |
|
||||
| "Add i18n / translate" | `05-08-i18n-guidelines.md` | i18n keys only — no hardcoded text |
|
||||
| "Workflow / DSL" | `ADR-001`, `01-03-modules/01-03-06-unified-workflow.md` | DSL state machine + WorkflowEngineService |
|
||||
| "Document numbering" | `ADR-002`, `01-02-business-rules/01-02-02-doc-numbering-rules.md` | Redis Redlock + DB optimistic lock (double-lock) |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Change Log
|
||||
- 1.9.0: Sync with AGENTS.md v1.9.0 (TS Standards, Hybrid Specs).
|
||||
- 1.8.5: Legacy version.
|
||||
|
||||
| Version | Date | Changes | Updated By |
|
||||
| ------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||
| 1.9.3 | 2026-05-16 | Sync with AGENTS.md v1.9.3 — ADR-023A updates, schema v1.9.0 path corrections, CaslModule import fix for DelegationModule, console.log → Logger migration | Windsurf AI |
|
||||
| 1.9.0 | 2026-05-13 | Sync with AGENTS.md v1.9.0 — TS Standards, Hybrid Specs organization | Windsurf AI |
|
||||
| 1.8.5 | 2026-04-22 | Legacy version | Human Dev |
|
||||
|
||||
+1
-25
@@ -1,25 +1 @@
|
||||
echo "🔍 Running pre-commit checks..."
|
||||
|
||||
# 1. Run lint-staged (Fast, only staged files)
|
||||
# Note: lint-staged returns 0 if no files match patterns by default
|
||||
pnpm lint-staged
|
||||
|
||||
# 2. Additional Global Safety Checks (Per t2.md) - Optimized for staged files
|
||||
# Use || true to prevent script exit if grep finds nothing for the file list
|
||||
staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx|js|jsx)$' | grep -E '^(backend|frontend)/') || true
|
||||
|
||||
if [ -n "$staged_files" ]; then
|
||||
# UUID misuse check
|
||||
grep -l "parseInt(.*uuid" $staged_files && {
|
||||
echo "❌ UUID misuse detected (parseInt) in staged files"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# console.log check
|
||||
grep -l "console.log" $staged_files && {
|
||||
echo "❌ console.log is not allowed in staged files"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
echo "✅ Pre-commit passed"
|
||||
pnpm test
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"globs": ["**/*.md", "!**/node_modules/**"],
|
||||
"default": true,
|
||||
"MD009": { "br_spaces": 2, "strict": false },
|
||||
"MD013": false,
|
||||
"MD033": false,
|
||||
"MD041": false,
|
||||
"MD022": false,
|
||||
"MD031": false,
|
||||
"MD032": false,
|
||||
"MD040": false,
|
||||
"MD036": false,
|
||||
"MD026": false,
|
||||
"MD029": false,
|
||||
"MD060": false,
|
||||
"MD024": {
|
||||
"siblings_only": true
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ Best practice — follow when possible:
|
||||
|
||||
---
|
||||
|
||||
## 📐 TypeScript Rules & Coding Standards (v1.9.0)
|
||||
## 📐 TypeScript Rules & Coding Standards (v1.9.3)
|
||||
|
||||
### 📝 Core Standards
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ trigger: always_on
|
||||
- [ ] No SQL injection vulnerabilities
|
||||
- [ ] File upload validation (whitelist + ClamAV)
|
||||
- [ ] Rate limiting applied to auth endpoints
|
||||
- [ ] AI boundary enforcement (ADR-018) - no direct DB/storage access
|
||||
- [ ] AI boundary enforcement (ADR-023) - no direct DB/storage access
|
||||
- [ ] AI audit logging implemented for AI interactions
|
||||
- [ ] AI outputs validated before use (human-in-the-loop)
|
||||
- [ ] Error handling follows ADR-007 layered classification
|
||||
- [ ] Cache invalidation when data modified
|
||||
- [ ] OWASP Top 10 review passed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# TypeScript Rules (v1.9.0)
|
||||
# TypeScript Rules (v1.9.3)
|
||||
|
||||
## Core Standards
|
||||
|
||||
@@ -13,10 +13,18 @@
|
||||
## File & Function Structure
|
||||
|
||||
- **File Headers** — every file MUST start with `// File: path/filename` on the first line.
|
||||
- Use **absolute path** from project root (e.g., `// File: backend/src/modules/correspondence/correspondence.service.ts`)
|
||||
- Do NOT use relative path (e.g., `// File: src/example.service.ts`)
|
||||
- **Change Log** — include `// Change Log` at the top of the file.
|
||||
- **Single Export** — export **only one main symbol** per file.
|
||||
- **Function Style** — avoid unnecessary blank lines inside functions.
|
||||
|
||||
## i18n Guidelines
|
||||
|
||||
- **No Hardcoded Text:** Use i18n keys for all user-facing text
|
||||
- **Reference:** `specs/05-Engineering-Guidelines/05-08-i18n-guidelines.md`
|
||||
- **Pattern:** Use `t('key.path')` from i18n hook instead of hardcoded strings
|
||||
|
||||
## Patterns
|
||||
|
||||
```typescript
|
||||
|
||||
@@ -29,10 +29,11 @@ Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > oth
|
||||
| Document | Path | Use When |
|
||||
| ----------------------- | ----------------------------------------------------------------- | ------------------------------- |
|
||||
| **Glossary** | `specs/00-overview/00-02-glossary.md` | Verify domain terminology |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | Before writing any query |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` | Before writing any query |
|
||||
| **Data Dictionary** | `specs/03-Data-and-Storage/03-01-data-dictionary.md` | Field meanings + business rules |
|
||||
| **Edge Cases** | `specs/01-Requirements/01-06-edge-cases-and-rules.md` | Prevent bugs in flows |
|
||||
| **ADR-019 UUID** | `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | UUID-related work |
|
||||
| **ADR-023 AI** | `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md` | AI integration work |
|
||||
| **Backend Guidelines** | `specs/05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns |
|
||||
| **Frontend Guidelines** | `specs/05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns |
|
||||
| **Testing Strategy** | `specs/05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals |
|
||||
|
||||
@@ -7,27 +7,29 @@ trigger: always_on
|
||||
## ❌ Never Do This
|
||||
|
||||
| ❌ Forbidden | ✅ Correct Approach |
|
||||
| ----------------------------------------------- | ----------------------------------------------- |
|
||||
| ----------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| SQL Triggers for business logic | NestJS Service methods |
|
||||
| `.env` files in production | `docker-compose.yml` environment section |
|
||||
| TypeORM migration files | Edit schema SQL directly (ADR-009) |
|
||||
| Inventing table/column names | Verify against `schema-02-tables.sql` |
|
||||
| Inventing table/column names | Verify against `lcbp3-v1.9.0-schema-02-tables.sql` |
|
||||
| `any` TypeScript type | Proper types / generics |
|
||||
| `console.log` in committed code | NestJS Logger (backend) / remove (frontend) |
|
||||
| `req: any` in controllers | `RequestWithUser` typed interface |
|
||||
| `parseInt()` on UUID values | Use UUID string directly (ADR-019) |
|
||||
| Exposing INT PK in API responses | UUIDv7 (ADR-019) |
|
||||
| AI accessing DB/storage directly | AI → DMS API → DB (ADR-018) |
|
||||
| AI accessing DB/storage directly | AI → DMS API → DB (ADR-023) |
|
||||
| Direct file operations bypassing StorageService | `StorageService` for all file moves |
|
||||
| Inline email/notification sending | BullMQ queue job |
|
||||
| Deploying without Release Gates | Complete `04-08-release-management-policy.md` |
|
||||
| AI direct cloud API calls | On-premises Ollama only (ADR-018) |
|
||||
| AI outputs without human validation | Human-in-the-loop validation required (ADR-020) |
|
||||
| AI direct cloud API calls | On-premises Ollama only (ADR-023) |
|
||||
| AI outputs without human validation | Human-in-the-loop validation required (ADR-023) |
|
||||
| n8n calling Ollama/Qdrant directly | n8n → DMS API → BullMQ → Ollama/Qdrant (ADR-023A) |
|
||||
| Qdrant query without projectPublicId filter | QdrantService.search(projectPublicId: string) required (ADR-023A) |
|
||||
|
||||
## Schema Changes (ADR-009)
|
||||
|
||||
- **NO TypeORM migrations** — edit SQL schema directly
|
||||
- Always check `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` before writing queries
|
||||
- Always check `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` before writing queries
|
||||
- Update Data Dictionary when changing fields
|
||||
|
||||
## UUID Handling
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
trigger: glob
|
||||
globs:
|
||||
- "backend/**/*.service.ts"
|
||||
- "backend/**/*.controller.ts"
|
||||
- "backend/**/*.dto.ts"
|
||||
- "backend/**/*.entity.ts"
|
||||
- 'backend/**/*.service.ts'
|
||||
- 'backend/**/*.controller.ts'
|
||||
- 'backend/**/*.dto.ts'
|
||||
- 'backend/**/*.entity.ts'
|
||||
---
|
||||
|
||||
# Backend Patterns (NestJS)
|
||||
@@ -49,7 +49,7 @@ class Contract extends UuidBaseEntity {
|
||||
@Column({ type: 'uuid' })
|
||||
publicId: string;
|
||||
|
||||
@PrimaryKey()
|
||||
@PrimaryGeneratedColumn()
|
||||
@Exclude()
|
||||
id: number;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ trigger: always_on
|
||||
|
||||
1. **Glossary check** — verify domain terms in `00-02-glossary.md`
|
||||
2. **Read the spec** — select from Key Spec Files table
|
||||
3. **Check schema** — verify table/column in `schema-02-tables.sql`
|
||||
3. **Check schema** — verify table/column in `lcbp3-v1.9.0-schema-02-tables.sql`
|
||||
4. **Check data dictionary** — confirm field meanings + business rules
|
||||
5. **Scan edge cases** — `01-06-edge-cases-and-rules.md`
|
||||
6. **Check ADRs** — verify decisions align (ADR-009, ADR-018, ADR-019)
|
||||
@@ -35,10 +35,10 @@ trigger: always_on
|
||||
## Context-Aware Triggers
|
||||
|
||||
| Request | Files to Check | Expected Response |
|
||||
| -------------------- | ------------------------------------------------------- | --------------------------------------------------- |
|
||||
| "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard |
|
||||
| -------------------- | -------------------------------------------------------------------- | --------------------------------------------------- |
|
||||
| "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `lcbp3-v1.9.0-schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard |
|
||||
| "แก้ฟอร์ม frontend" | `05-03-frontend-guidelines.md`, `01-06-edge-cases.md` | RHF+Zod + TanStack Query + Thai comments |
|
||||
| "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `schema-02-tables.sql` | Edit SQL directly + update Data Dictionary + Entity |
|
||||
| "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `lcbp3-v1.9.0-schema-02-tables.sql` | Edit SQL directly + update Data Dictionary + Entity |
|
||||
| "ตรวจสอบ UUID" | `ADR-019`, `05-07-hybrid-uuid-implementation-plan.md` | UUIDv7 MariaDB native UUID + TransformInterceptor |
|
||||
| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | Edit SQL schema directly + n8n workflow |
|
||||
| "ตรวจสอบ permission" | `seed-permissions.sql`, `ADR-016` | CASL 4-Level RBAC matrix |
|
||||
|
||||
@@ -2,32 +2,37 @@
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# ADR-020 AI Integration Architecture
|
||||
# ADR-023/023A AI Integration Architecture
|
||||
|
||||
## CRITICAL RULES
|
||||
|
||||
- **ALWAYS** follow ADR-018 AI boundary policy (isolation on Admin Desktop)
|
||||
- **ALWAYS** use RFA-First approach for AI implementation
|
||||
- **ALWAYS** follow ADR-023 AI boundary policy (isolation on Admin Desktop)
|
||||
- **ALWAYS** use ADR-023A 2-model stack (gemma4:e4b Q8_0 + nomic-embed-text)
|
||||
- **ALWAYS** use BullMQ 2-queue (ai-realtime + ai-batch) for GPU overload prevention
|
||||
- **NEVER** allow AI direct database/storage access
|
||||
- **ALWAYS** implement human-in-the-loop validation
|
||||
- **NEVER** send sensitive data to cloud AI services
|
||||
- **ALWAYS** enforce Qdrant projectPublicId filter (compile-time enforcement)
|
||||
- **NEVER** allow n8n to call Ollama/Qdrant directly (must go through DMS API → BullMQ)
|
||||
|
||||
## AI Integration Patterns
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
```
|
||||
Frontend → AI Gateway API → Admin Desktop (Ollama) → Backend Validation
|
||||
Frontend → AI Gateway API → BullMQ → Admin Desktop (Ollama) → Backend Validation
|
||||
n8n (Migration) → DMS API → BullMQ → Admin Desktop (Ollama) → Backend Validation
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| ----------------- | ------------------------- | ------------------------------------------------------------------------ |
|
||||
| **AI Gateway** | Backend (NestJS) | API endpoints, validation, audit logging |
|
||||
| **Ollama Engine** | Admin Desktop (Desk-5439) | LLM inference (Gemma 4) |
|
||||
| **OCR Engine** | Admin Desktop (Desk-5439) | Thai/English text extraction |
|
||||
| **Orchestrator** | QNAP NAS (n8n) | Workflow management |
|
||||
| **BullMQ Queues** | Backend (NestJS) | ai-realtime (RAG/Suggest), ai-batch (OCR/Extract/Embed) |
|
||||
| **Ollama Engine** | Admin Desktop (Desk-5439) | gemma4:e4b Q8_0 (LLM) + nomic-embed-text (Embedding) |
|
||||
| **OCR Engine** | Admin Desktop (Desk-5439) | PaddleOCR + PyThaiNLP (Thai/English text extraction) |
|
||||
| **Orchestrator** | QNAP NAS (n8n) | Migration Phase orchestrator only (calls DMS API, never Ollama directly) |
|
||||
|
||||
## Backend Implementation (NestJS)
|
||||
|
||||
@@ -35,24 +40,50 @@ Frontend → AI Gateway API → Admin Desktop (Ollama) → Backend Validation
|
||||
// AI Module with boundary enforcement
|
||||
@Module({
|
||||
controllers: [AiController],
|
||||
providers: [AiService, AiGateway],
|
||||
providers: [AiService, AiGateway, QdrantService],
|
||||
exports: [AiService],
|
||||
})
|
||||
export class AiModule {
|
||||
constructor() {
|
||||
// Enforce ADR-018 boundaries
|
||||
// Enforce ADR-023 boundaries
|
||||
}
|
||||
}
|
||||
|
||||
// QdrantService with compile-time projectPublicId enforcement
|
||||
@Injectable()
|
||||
export class QdrantService {
|
||||
async search(
|
||||
projectPublicId: string, // required — compile-time enforcement
|
||||
vector: number[],
|
||||
topK: number = 5,
|
||||
): Promise<QdrantSearchResult[]> {
|
||||
return this.client.search('documents', {
|
||||
vector,
|
||||
limit: topK,
|
||||
filter: {
|
||||
must: [{ key: 'project_public_id', match: { value: projectPublicId } }],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async upsert(
|
||||
projectPublicId: string, // required
|
||||
chunks: DocumentChunk[],
|
||||
): Promise<void> { ... }
|
||||
|
||||
// ❌ NEVER expose rawSearch() or method without projectPublicId filter
|
||||
}
|
||||
|
||||
// AI Service with validation
|
||||
@Injectable()
|
||||
export class AiService {
|
||||
async extractMetadata(documentId: string): Promise<AIMetadata> {
|
||||
// 1. Validate permissions
|
||||
// 2. Send to Admin Desktop AI
|
||||
// 3. Validate AI response
|
||||
// 4. Log audit trail
|
||||
// 5. Return validated results
|
||||
// 2. Queue job to BullMQ (ai-batch or ai-realtime)
|
||||
// 3. Worker sends to Admin Desktop AI (gemma4:e4b Q8_0)
|
||||
// 4. Validate AI response
|
||||
// 5. Log audit trail to ai_audit_logs
|
||||
// 6. Return validated results
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -77,24 +108,37 @@ const DocumentReviewForm = ({ document, aiSuggestions }) => {
|
||||
|
||||
## Security Requirements
|
||||
|
||||
- **AI Isolation:** All AI processing on Admin Desktop only
|
||||
- **AI Isolation:** All AI processing on Admin Desktop only (Desk-5439)
|
||||
- **Data Privacy:** No cloud AI services, on-premises only
|
||||
- **Audit Trail:** Log all AI interactions and human validations
|
||||
- **Audit Trail:** Log all AI interactions and human validations to ai_audit_logs
|
||||
- **Rate Limiting:** Prevent AI abuse and resource exhaustion
|
||||
- **Validation:** All AI outputs must be validated before use
|
||||
- **Multi-tenant Isolation:** Qdrant queries MUST include projectPublicId filter (compile-time enforcement)
|
||||
- **n8n Boundary:** n8n MUST call DMS API → BullMQ, NEVER Ollama/Qdrant directly
|
||||
- **GPU Overload Prevention:** BullMQ 2-queue (ai-realtime + ai-batch) with concurrency=1
|
||||
|
||||
## ADR-023A Specific Rules
|
||||
|
||||
- **2-Model Stack:** gemma4:e4b Q8_0 (~4.0GB) + nomic-embed-text (~0.3GB) = ~4.3GB VRAM peak
|
||||
- **PDF 3-Page Limit:** Classification/Tagging uses first 3 pages only (NOT RAG embedding)
|
||||
- **RAG Embedding:** Full document chunked at 512 tokens/64 tokens overlap
|
||||
- **OCR Auto-Detect:** PyMuPDF chars > 100 → Fast path, else PaddleOCR
|
||||
- **Embed Auto-Trigger:** AUTO after commit (parallel), gap covered by DB search
|
||||
- **Threshold Recalibration:** After 100-500 docs, based on ai_audit_logs analysis
|
||||
|
||||
## Required Implementation
|
||||
|
||||
- [ ] AiModule with ADR-018 boundary enforcement
|
||||
- [ ] AiModule with ADR-023 boundary enforcement
|
||||
- [ ] AI Gateway API endpoints with validation
|
||||
- [ ] BullMQ 2-queue setup (ai-realtime + ai-batch)
|
||||
- [ ] QdrantService with projectPublicId enforcement
|
||||
- [ ] DocumentReviewForm reusable component
|
||||
- [ ] Admin Desktop Ollama + PaddleOCR setup
|
||||
- [ ] n8n workflow orchestration
|
||||
- [ ] AI audit logging and monitoring
|
||||
- [ ] Admin Desktop Ollama (gemma4:e4b Q8_0 + nomic-embed-text) + PaddleOCR setup
|
||||
- [ ] n8n workflow orchestration (Migration Phase only)
|
||||
- [ ] AI audit logging and monitoring (ai_audit_logs)
|
||||
- [ ] Human-in-the-loop validation workflows
|
||||
|
||||
## Related Documents
|
||||
|
||||
- `specs/06-Decision-Records/ADR-018-ai-boundary.md`
|
||||
- `specs/06-Decision-Records/ADR-020-ai-intelligence-integration.md`
|
||||
- `specs/06-Decision-Records/ADR-017-ollama-data-migration.md`
|
||||
- `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md` (Base architecture)
|
||||
- `specs/06-Decision-Records/ADR-023A-unified-ai-architecture.md` (Model revision - current)
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# LCBP3 Agent Rules
|
||||
|
||||
Critical rules and guidelines for AI agents working on LCBP3-DMS.
|
||||
|
||||
## Version
|
||||
|
||||
- **Current:** v1.9.3
|
||||
- **Last Updated:** 2026-05-15
|
||||
- **Synced with:** `AGENTS.md` (v1.9.3)
|
||||
|
||||
## Purpose
|
||||
|
||||
This directory contains rule files that define:
|
||||
- Project context and role expectations
|
||||
- Critical Tier 1 rules (CI blockers)
|
||||
- Coding standards and patterns
|
||||
- Domain terminology and glossary
|
||||
- Development workflows
|
||||
- Security requirements
|
||||
- AI integration architecture (ADR-023/023A)
|
||||
|
||||
## Rule Enforcement Tiers
|
||||
|
||||
### 🔴 Tier 1 — CRITICAL (CI BLOCKER)
|
||||
|
||||
Build fails immediately if violated:
|
||||
- Security (Auth, RBAC, Validation)
|
||||
- UUID Strategy (ADR-019) — no `parseInt` / `Number` / `+` on UUID
|
||||
- Database correctness — verify schema before writing queries
|
||||
- File upload security (ClamAV + whitelist)
|
||||
- AI validation boundary (ADR-023)
|
||||
- Error handling strategy (ADR-007)
|
||||
- Forbidden patterns: `any`, `console.log`, UUID misuse, `id ?? ''` fallback
|
||||
|
||||
### 🟡 Tier 2 — IMPORTANT (CODE REVIEW)
|
||||
|
||||
Must fix before merge:
|
||||
- Architecture patterns (thin controller, business logic in service)
|
||||
- Test coverage (80%+ business logic, 70%+ backend overall)
|
||||
- Cache invalidation
|
||||
- Naming conventions
|
||||
- TypeScript Standards: Missing JSDoc, explicit types, or file headers
|
||||
|
||||
### 🟢 Tier 3 — GUIDELINES
|
||||
|
||||
Best practice — follow when possible:
|
||||
- Code style / formatting (Prettier handles)
|
||||
- Comment completeness
|
||||
- Minor optimizations
|
||||
|
||||
## Rule Files
|
||||
|
||||
### Core Rules (Tier 1 - CRITICAL)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `00-project-context.md` | Project context, role & persona, tier classification, specs folder organization |
|
||||
| `01-adr-019-uuid.md` | UUID handling strategy — no parseInt, use publicId only |
|
||||
| `02-security.md` | Security requirements, checklist, ADR-023/023A AI boundaries |
|
||||
|
||||
### Coding Standards
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `03-typescript.md` | TypeScript rules, file headers, i18n guidelines |
|
||||
| `06-backend-patterns.md` | NestJS patterns, UUID resolution, API response patterns |
|
||||
| `07-frontend-patterns.md` | Next.js patterns, RHF+Zod+TanStack Query, UUID handling |
|
||||
|
||||
### Domain & Workflow
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `04-domain-terminology.md` | DMS glossary, key spec files priority table |
|
||||
| `08-development-flow.md` | Development workflow by work type (Critical/Normal/Quick Fix) |
|
||||
|
||||
### Compliance & Architecture
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `05-forbidden-actions.md` | Actions that must never be done, schema changes, UUID handling |
|
||||
| `09-commit-checklist.md` | Pre-commit verification, commit message format |
|
||||
| `10-error-handling.md` | ADR-007 error handling strategy, layered classification |
|
||||
| `11-ai-integration.md` | ADR-023/023A AI architecture, 2-model stack, BullMQ 2-queue |
|
||||
|
||||
## Key Spec Files Priority
|
||||
|
||||
Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others
|
||||
|
||||
| Document | Path | Use When |
|
||||
|----------|------|----------|
|
||||
| **Glossary** | `specs/00-overview/00-02-glossary.md` | Verify domain terminology |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` | Before writing any query |
|
||||
| **Data Dictionary** | `specs/03-Data-and-Storage/03-01-data-dictionary.md` | Field meanings + business rules |
|
||||
| **Edge Cases** | `specs/01-Requirements/01-06-edge-cases-and-rules.md` | Prevent bugs in flows |
|
||||
| **ADR-019 UUID** | `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | UUID-related work |
|
||||
| **ADR-023 AI** | `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md` | AI integration work |
|
||||
| **Backend Guidelines** | `specs/05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns |
|
||||
| **Frontend Guidelines** | `specs/05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns |
|
||||
| **Testing Strategy** | `specs/05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals |
|
||||
|
||||
## Maintenance
|
||||
|
||||
When updating rules:
|
||||
|
||||
1. **Check AGENTS.md version** — Ensure rule files are synced
|
||||
2. **Update version numbers** — Bump version in `00-project-context.md` and `03-typescript.md`
|
||||
3. **Review ADR references** — Ensure all ADR references are current (ADR-023, ADR-023A, etc.)
|
||||
4. **Add new forbidden actions** — When new patterns are identified as violations
|
||||
5. **Update key spec files table** — When new ADRs or guidelines are added
|
||||
|
||||
## Related Documents
|
||||
|
||||
- `AGENTS.md` — Master agent configuration and context
|
||||
- `specs/06-Decision-Records/` — All Architecture Decision Records
|
||||
- `specs/05-Engineering-Guidelines/` — Backend, frontend, and testing guidelines
|
||||
@@ -1,6 +1,6 @@
|
||||
# `.agents/skills/` — LCBP3 Agent Skill Pack
|
||||
|
||||
**Version:** 1.8.9 | **Last Updated:** 2026-04-22 | **Total Skills:** 20
|
||||
**Version:** 1.9.0 | **Last Updated:** 2026-05-17 | **Total Skills:** 23
|
||||
|
||||
Agent skills for AI-assisted development in **Windsurf IDE** (and compatible agents: Codex CLI, opencode, Amp, Antigravity, AGENTS.md-aware tools).
|
||||
|
||||
@@ -16,12 +16,15 @@ Agent skills for AI-assisted development in **Windsurf IDE** (and compatible age
|
||||
├── README.md # (this file)
|
||||
├── nestjs-best-practices/ # Backend rules (40 rules across 10 categories)
|
||||
├── next-best-practices/ # Frontend rules (Next.js 15+)
|
||||
├── e2e-testing/ # Playwright E2E testing patterns (POM, flaky tests, CI/CD)
|
||||
├── verification-loop/ # Comprehensive verification (build, typecheck, lint, test, security)
|
||||
├── security-review/ # OWASP Top 10 + ADR compliance checklist
|
||||
└── speckit-*/ # 18 workflow skills (spec → plan → tasks → implement → …)
|
||||
```
|
||||
|
||||
Each skill directory contains:
|
||||
|
||||
- `SKILL.md` — frontmatter (`name`, `description`, `version: 1.8.9`, `scope`, `depends-on`, `handoffs`) + instructions
|
||||
- `SKILL.md` — frontmatter (`name`, `description`, `version: 1.9.0`, `scope`, `depends-on`, `handoffs`) + instructions
|
||||
- `templates/` _(optional)_ — artifact templates (spec/plan/tasks/checklist)
|
||||
- `rules/` _(nestjs only)_ — individual rule files grouped by prefix (`arch-`, `security-`, `db-`, etc.)
|
||||
|
||||
@@ -63,7 +66,7 @@ Use `/00-speckit.all` to run specify → clarify → plan → tasks → analyze
|
||||
From repo root:
|
||||
|
||||
| Script | Purpose |
|
||||
| --- | --- |
|
||||
| --------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| `./.agents/scripts/bash/check-prerequisites.sh --json` | Emit `FEATURE_DIR` + `AVAILABLE_DOCS` for a feature branch |
|
||||
| `./.agents/scripts/bash/setup-plan.sh --json` | Emit `FEATURE_SPEC`, `IMPL_PLAN`, `SPECS_DIR`, `BRANCH` |
|
||||
| `./.agents/scripts/bash/update-agent-context.sh windsurf` | Append tech entries to `AGENTS.md` |
|
||||
@@ -92,7 +95,7 @@ See [`_LCBP3-CONTEXT.md`](./_LCBP3-CONTEXT.md) for the complete list.
|
||||
|
||||
To add a new skill:
|
||||
|
||||
1. Create `NAME/SKILL.md` with frontmatter: `name`, `description`, `version: 1.8.9`, `scope`, `depends-on`.
|
||||
1. Create `NAME/SKILL.md` with frontmatter: `name`, `description`, `version: 1.9.0`, `scope`, `depends-on`.
|
||||
2. Append an LCBP3 context reference pointing to `_LCBP3-CONTEXT.md`.
|
||||
3. Wrap with `.windsurf/workflows/NAME.md` so it becomes a slash command.
|
||||
4. Update [`skills.md`](./skills.md) dependency matrix.
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
## 🔴 Tier 1 Non-Negotiables
|
||||
|
||||
- **ADR-019 UUID:** `publicId: string` exposed directly — **no** `@Expose({ name: 'id' })` rename; **no** `parseInt`/`Number`/`+` on UUID; **no** `id ?? ''` fallback in frontend.
|
||||
- **ADR-009:** No TypeORM migrations — edit `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` or add a `deltas/*.sql` file.
|
||||
- **ADR-009:** No TypeORM migrations — edit `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` or add a `deltas/*.sql` file.
|
||||
- **ADR-016 Security:** JWT + CASL 4-Level RBAC; `@UseGuards(JwtAuthGuard, CaslAbilityGuard)` on every mutation controller; `ThrottlerGuard` on auth; bcrypt 12 rounds; `Idempotency-Key` required on POST/PUT/PATCH.
|
||||
- **ADR-002 Document Numbering:** Redis Redlock + TypeORM `@VersionColumn` (double-lock). Never use application-side counter alone.
|
||||
- **ADR-008 Notifications:** BullMQ queue — never inline email/notification in a request thread.
|
||||
@@ -59,7 +59,7 @@
|
||||
| A plan | `.agents/skills/speckit-plan/templates/plan-template.md` + relevant ADRs |
|
||||
| Task breakdown | `.agents/skills/speckit-tasks/templates/tasks-template.md` + existing patterns in `specs/08-Tasks/` |
|
||||
| Acceptance criteria / UAT | `specs/01-Requirements/01-05-acceptance-criteria.md` |
|
||||
| Schema / table definition | `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` + `03-01-data-dictionary.md` |
|
||||
| Schema / table definition | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` + `03-01-data-dictionary.md` |
|
||||
| RBAC / permissions | `specs/03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` + `01-02-01-rbac-matrix.md` |
|
||||
| Release / hotfix | `specs/04-Infrastructure-OPS/04-08-release-management-policy.md` |
|
||||
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
---
|
||||
name: e2e-testing
|
||||
description: Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies for LCBP3-DMS.
|
||||
version: 1.9.0
|
||||
scope: testing
|
||||
depends-on: []
|
||||
handoffs-to: [speckit-tester]
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# E2E Testing Skill
|
||||
|
||||
Playwright E2E testing patterns adapted for LCBP3-DMS (NestJS + Next.js + MariaDB stack).
|
||||
|
||||
## LCBP3 Context
|
||||
|
||||
See [`_LCBP3-CONTEXT.md`](../_LCBP3-CONTEXT.md) for project-specific testing requirements:
|
||||
- Backend: Jest (Unit + Integration + E2E)
|
||||
- Frontend: Vitest (Unit) + Playwright (E2E)
|
||||
- E2E test location: `frontend/e2e/workflow-adr021.spec.ts`
|
||||
- Coverage goals: Backend 70%+, Business Logic 80%+
|
||||
|
||||
## When to Use
|
||||
|
||||
Invoke this skill when:
|
||||
- Creating new E2E tests for frontend features
|
||||
- Debugging flaky Playwright tests
|
||||
- Setting up CI/CD integration for E2E tests
|
||||
- Optimizing test performance and reliability
|
||||
- Implementing Page Object Model (POM) patterns
|
||||
|
||||
## Test File Organization
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── e2e/
|
||||
│ ├── auth/
|
||||
│ │ ├── login.spec.ts
|
||||
│ │ └── logout.spec.ts
|
||||
│ ├── correspondence/
|
||||
│ │ ├── create.spec.ts
|
||||
│ │ └── workflow.spec.ts
|
||||
│ ├── transmittals/
|
||||
│ │ ├── create.spec.ts
|
||||
│ │ └── submit.spec.ts
|
||||
│ ├── circulation/
|
||||
│ │ ├── routing.spec.ts
|
||||
│ │ └── approval.spec.ts
|
||||
│ └── workflow-adr021.spec.ts # Existing ADR-021 integration test
|
||||
├── playwright.config.ts
|
||||
└── tests/
|
||||
└── fixtures/
|
||||
├── auth.ts
|
||||
└── data.ts
|
||||
```
|
||||
|
||||
## Page Object Model (POM)
|
||||
|
||||
```typescript
|
||||
// frontend/e2e/pages/CorrespondencePage.ts
|
||||
import { Page, Locator } from '@playwright/test'
|
||||
|
||||
export class CorrespondencePage {
|
||||
readonly page: Page
|
||||
readonly createButton: Locator
|
||||
readonly subjectInput: Locator
|
||||
readonly recipientSelect: Locator
|
||||
readonly submitButton: Locator
|
||||
readonly successMessage: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.createButton = page.getByTestId('create-correspondence')
|
||||
this.subjectInput = page.getByTestId('subject-input')
|
||||
this.recipientSelect = page.getByTestId('recipient-select')
|
||||
this.submitButton = page.getByTestId('submit-button')
|
||||
this.successMessage = page.getByTestId('success-message')
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/admin/doc-control/correspondences')
|
||||
await this.page.waitForLoadState('networkidle')
|
||||
}
|
||||
|
||||
async createCorrespondence(data: {
|
||||
subject: string
|
||||
recipientId: string
|
||||
}) {
|
||||
await this.createButton.click()
|
||||
await this.subjectInput.fill(data.subject)
|
||||
await this.recipientSelect.selectOption(data.recipientId)
|
||||
await this.submitButton.click()
|
||||
}
|
||||
|
||||
async verifySuccess() {
|
||||
await expect(this.successMessage).toBeVisible()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
```typescript
|
||||
// frontend/e2e/correspondence/create.spec.ts
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { CorrespondencePage } from '../pages/CorrespondencePage'
|
||||
|
||||
test.describe('Correspondence Creation', () => {
|
||||
let correspondencePage: CorrespondencePage
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
correspondencePage = new CorrespondencePage(page)
|
||||
await correspondencePage.goto()
|
||||
})
|
||||
|
||||
test('should create correspondence successfully', async ({ page }) => {
|
||||
await correspondencePage.createCorrespondence({
|
||||
subject: 'Test Correspondence',
|
||||
recipientId: 'test-recipient-id'
|
||||
})
|
||||
|
||||
await correspondencePage.verifySuccess()
|
||||
await page.screenshot({ path: 'artifacts/correspondence-created.png' })
|
||||
})
|
||||
|
||||
test('should validate required fields', async ({ page }) => {
|
||||
await correspondencePage.createButton.click()
|
||||
await correspondencePage.submitButton.click()
|
||||
|
||||
await expect(page.getByTestId('subject-error')).toBeVisible()
|
||||
await expect(page.getByTestId('recipient-error')).toBeVisible()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Playwright Configuration
|
||||
|
||||
```typescript
|
||||
// frontend/playwright.config.ts
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: [
|
||||
['html', { outputFolder: 'playwright-report' }],
|
||||
['junit', { outputFile: 'playwright-results.xml' }],
|
||||
['json', { outputFile: 'playwright-results.json' }]
|
||||
],
|
||||
use: {
|
||||
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
actionTimeout: 10000,
|
||||
navigationTimeout: 30000,
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||
],
|
||||
webServer: {
|
||||
command: 'pnpm dev',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120000,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Flaky Test Patterns
|
||||
|
||||
### Quarantine
|
||||
|
||||
```typescript
|
||||
test('flaky: complex workflow', async ({ page }) => {
|
||||
test.fixme(true, 'Flaky - Issue #123')
|
||||
// test code...
|
||||
})
|
||||
|
||||
test('conditional skip', async ({ page }) => {
|
||||
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
|
||||
// test code...
|
||||
})
|
||||
```
|
||||
|
||||
### Identify Flakiness
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npx playwright test e2e/correspondence/create.spec.ts --repeat-each=10
|
||||
npx playwright test e2e/correspondence/create.spec.ts --retries=3
|
||||
```
|
||||
|
||||
### Common Causes & Fixes
|
||||
|
||||
**Race conditions:**
|
||||
```typescript
|
||||
// Bad: assumes element is ready
|
||||
await page.click('[data-testid="submit-button"]')
|
||||
|
||||
// Good: auto-wait locator
|
||||
await page.locator('[data-testid="submit-button"]').click()
|
||||
```
|
||||
|
||||
**Network timing:**
|
||||
```typescript
|
||||
// Bad: arbitrary timeout
|
||||
await page.waitForTimeout(5000)
|
||||
|
||||
// Good: wait for specific condition
|
||||
await page.waitForResponse(resp =>
|
||||
resp.url().includes('/api/correspondences') && resp.status() === 201
|
||||
)
|
||||
```
|
||||
|
||||
**Animation timing:**
|
||||
```typescript
|
||||
// Bad: click during animation
|
||||
await page.click('[data-testid="menu-item"]')
|
||||
|
||||
// Good: wait for stability
|
||||
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.locator('[data-testid="menu-item"]').click()
|
||||
```
|
||||
|
||||
## Artifact Management
|
||||
|
||||
### Screenshots
|
||||
|
||||
```typescript
|
||||
await page.screenshot({ path: 'artifacts/after-login.png' })
|
||||
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
|
||||
await page.locator('[data-testid="workflow-banner"]').screenshot({
|
||||
path: 'artifacts/workflow-banner.png'
|
||||
})
|
||||
```
|
||||
|
||||
### Traces
|
||||
|
||||
```typescript
|
||||
// In playwright.config.ts
|
||||
use: {
|
||||
trace: 'on-first-retry'
|
||||
}
|
||||
|
||||
// View trace
|
||||
npx playwright show-trace trace.zip
|
||||
```
|
||||
|
||||
### Video
|
||||
|
||||
```typescript
|
||||
// In playwright.config.ts
|
||||
use: {
|
||||
video: 'retain-on-failure',
|
||||
videosPath: 'artifacts/videos/'
|
||||
}
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
```yaml
|
||||
# .github/workflows/e2e.yml
|
||||
name: E2E Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: pnpm install
|
||||
- run: cd frontend && npx playwright install --with-deps
|
||||
- run: cd frontend && npx playwright test
|
||||
env:
|
||||
BASE_URL: ${{ vars.STAGING_URL }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: frontend/playwright-report/
|
||||
retention-days: 30
|
||||
```
|
||||
|
||||
## Test Report Template
|
||||
|
||||
```markdown
|
||||
# E2E Test Report
|
||||
|
||||
**Date:** YYYY-MM-DD HH:MM
|
||||
**Duration:** Xm Ys
|
||||
**Status:** PASSING / FAILING
|
||||
|
||||
## Summary
|
||||
- Total: X | Passed: Y (Z%) | Failed: A | Flaky: B | Skipped: C
|
||||
|
||||
## Failed Tests
|
||||
|
||||
### correspondence-create
|
||||
**File:** `frontend/e2e/correspondence/create.spec.ts:45`
|
||||
**Error:** Expected element to be visible
|
||||
**Screenshot:** artifacts/failed.png
|
||||
**Recommended Fix:** Add waitForLoadState after form submission
|
||||
|
||||
## Artifacts
|
||||
- HTML Report: frontend/playwright-report/index.html
|
||||
- Screenshots: frontend/artifacts/*.png
|
||||
- Videos: frontend/artifacts/videos/*.webm
|
||||
- Traces: frontend/artifacts/*.zip
|
||||
```
|
||||
|
||||
## Critical Flow Testing
|
||||
|
||||
```typescript
|
||||
// frontend/e2e/workflow/adr021.spec.ts
|
||||
test('workflow: correspondence → rfa → approval', async ({ page }) => {
|
||||
// Create correspondence
|
||||
await createCorrespondence(page)
|
||||
await expect(page.getByTestId('correspondence-created')).toBeVisible()
|
||||
|
||||
// Submit for RFA
|
||||
await page.getByTestId('submit-rfa').click()
|
||||
await expect(page.getByTestId('rfa-submitted')).toBeVisible()
|
||||
|
||||
// Approve RFA
|
||||
await page.goto('/admin/doc-control/rfa/123')
|
||||
await page.getByTestId('approve-button').click()
|
||||
await expect(page.getByTestId('approval-success')).toBeVisible()
|
||||
|
||||
// Verify workflow state
|
||||
await expect(page.getByTestId('workflow-state')).toContainText('APPROVED')
|
||||
})
|
||||
```
|
||||
|
||||
## LCBP3-Specific Considerations
|
||||
|
||||
- **UUID Handling:** Use `publicId` (string UUID) in E2E tests, never `parseInt()` (ADR-019)
|
||||
- **Authentication:** Mock auth tokens for E2E tests to avoid real auth flows
|
||||
- **Workflow States:** Test ADR-021 workflow transitions (DRAFT → PENDING → APPROVED)
|
||||
- **i18n:** Test with Thai language to verify i18n key resolution
|
||||
- **RBAC:** Test different user roles (admin, user, reviewer) for permission checks
|
||||
|
||||
## References
|
||||
|
||||
- LCBP3 Testing Strategy: `specs/05-Engineering-Guidelines/05-04-testing-strategy.md`
|
||||
- ADR-021 Workflow Context: `specs/06-Decision-Records/ADR-021-workflow-context.md`
|
||||
- Existing E2E test: `frontend/e2e/workflow-adr021.spec.ts`
|
||||
@@ -126,7 +126,7 @@ These rules override general NestJS best practices for the NAP-DMS project:
|
||||
### ADR-009: No TypeORM Migrations
|
||||
|
||||
- **ห้ามสร้างไฟล์ migration ของ TypeORM**
|
||||
- แก้ไข schema โดยตรงที่: `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql`
|
||||
- แก้ไข schema โดยตรงที่: `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql`
|
||||
- ใช้ n8n workflow สำหรับ data migration ถ้าจำเป็น
|
||||
|
||||
### ADR-019: Hybrid Identifier Strategy (CRITICAL — March 2026 Pattern)
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
---
|
||||
name: security-review
|
||||
description: Comprehensive security review for LCBP3-DMS with OWASP Top 10 checklist, ADR compliance, and automated security testing patterns.
|
||||
version: 1.9.0
|
||||
scope: security
|
||||
depends-on: []
|
||||
handoffs-to: [speckit-reviewer, speckit-security-audit]
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Security Review Skill
|
||||
|
||||
Comprehensive security review for LCBP3-DMS ensuring all code follows security best practices and identifies potential vulnerabilities.
|
||||
|
||||
## LCBP3 Context
|
||||
|
||||
See [`_LCBP3-CONTEXT.md`](../_LCBP3-CONTEXT.md) for project-specific security requirements:
|
||||
- **ADR-016**: Security & Authentication (JWT, CASL, RBAC, file upload)
|
||||
- **ADR-018**: AI Boundary (Ollama on Admin Desktop only, no direct DB/storage access)
|
||||
- **ADR-019**: UUID Strategy (no parseInt/Number/+ on UUID)
|
||||
- **ADR-023**: Unified AI Architecture (AI via DMS API only)
|
||||
- **ADR-007**: Error Handling (layered error classification)
|
||||
|
||||
## When to Activate
|
||||
|
||||
Invoke this skill:
|
||||
- Implementing authentication or authorization
|
||||
- Handling user input or file uploads
|
||||
- Creating new API endpoints
|
||||
- Working with secrets or credentials
|
||||
- Integrating AI features (Ollama/Qdrant)
|
||||
- Storing or transmitting sensitive data
|
||||
- Integrating third-party APIs
|
||||
|
||||
## Security Checklist
|
||||
|
||||
### 1. Secrets Management
|
||||
|
||||
#### FAIL: NEVER Do This
|
||||
```typescript
|
||||
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
|
||||
const dbPassword = "password123" // In source code
|
||||
```
|
||||
|
||||
#### PASS: ALWAYS Do This
|
||||
```typescript
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
const dbUrl = process.env.DATABASE_URL
|
||||
|
||||
// Verify secrets exist
|
||||
if (!apiKey) {
|
||||
throw new Error('OPENAI_API_KEY not configured')
|
||||
}
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] No hardcoded API keys, tokens, or passwords
|
||||
- [ ] All secrets in environment variables
|
||||
- [ ] `.env.local` in .gitignore
|
||||
- [ ] No secrets in git history
|
||||
- [ ] Production secrets in QNAP docker-compose environment section (not .env files)
|
||||
|
||||
### 2. Input Validation
|
||||
|
||||
#### Always Validate User Input
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
// Define validation schema
|
||||
const CreateCorrespondenceSchema = z.object({
|
||||
subject: z.string().min(1).max(500),
|
||||
recipientId: z.string().uuid(),
|
||||
typeCode: z.string().min(1).max(50)
|
||||
})
|
||||
|
||||
// Validate before processing
|
||||
export async function createCorrespondence(input: unknown) {
|
||||
try {
|
||||
const validated = CreateCorrespondenceSchema.parse(input)
|
||||
return await correspondenceService.create(validated)
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
throw new BadRequestException(error.errors)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### File Upload Validation (ADR-016)
|
||||
```typescript
|
||||
function validateFileUpload(file: Express.Multer.File) {
|
||||
// Size check (50MB max per ADR-016)
|
||||
const maxSize = 50 * 1024 * 1024
|
||||
if (file.size > maxSize) {
|
||||
throw new BadRequestException('File too large (max 50MB)')
|
||||
}
|
||||
|
||||
// Type check (whitelist: PDF, DWG, DOCX, XLSX, ZIP)
|
||||
const allowedTypes = [
|
||||
'application/pdf',
|
||||
'application/vnd.dwg',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/zip'
|
||||
]
|
||||
if (!allowedTypes.includes(file.mimetype)) {
|
||||
throw new BadRequestException('Invalid file type')
|
||||
}
|
||||
|
||||
// Extension check
|
||||
const allowedExtensions = ['.pdf', '.dwg', '.docx', '.xlsx', '.zip']
|
||||
const extension = path.extname(file.originalname).toLowerCase()
|
||||
if (!allowedExtensions.includes(extension)) {
|
||||
throw new BadRequestException('Invalid file extension')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] All user inputs validated with Zod (frontend) + class-validator (backend)
|
||||
- [ ] File uploads restricted (50MB max, whitelist types)
|
||||
- [ ] No direct use of user input in queries
|
||||
- [ ] Whitelist validation (not blacklist)
|
||||
- [ ] Error messages don't leak sensitive info
|
||||
|
||||
### 3. SQL Injection Prevention
|
||||
|
||||
#### FAIL: NEVER Concatenate SQL
|
||||
```typescript
|
||||
// DANGEROUS - SQL Injection vulnerability
|
||||
const query = `SELECT * FROM correspondences WHERE uuid = '${correspondenceUuid}'`
|
||||
await this.connection.query(query)
|
||||
```
|
||||
|
||||
#### PASS: ALWAYS Use TypeORM Parameterized Queries
|
||||
```typescript
|
||||
// Safe - TypeORM parameterized query
|
||||
const correspondence = await this.correspondenceRepository.findOne({
|
||||
where: { publicId: correspondenceUuid }
|
||||
})
|
||||
|
||||
// Or with QueryBuilder
|
||||
const result = await this.correspondenceRepository
|
||||
.createQueryBuilder('c')
|
||||
.where('c.publicId = :uuid', { uuid: correspondenceUuid })
|
||||
.getOne()
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] All database queries use TypeORM parameterized queries
|
||||
- [ ] No string concatenation in SQL
|
||||
- [ ] TypeORM query builder used correctly
|
||||
- [ ] Schema verified before writing queries (ADR-009)
|
||||
|
||||
### 4. Authentication & Authorization (ADR-016)
|
||||
|
||||
#### JWT Token Handling
|
||||
```typescript
|
||||
// FAIL: WRONG: localStorage (vulnerable to XSS)
|
||||
localStorage.setItem('token', token)
|
||||
|
||||
// PASS: CORRECT: httpOnly cookies
|
||||
response.setHeader('Set-Cookie',
|
||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`
|
||||
)
|
||||
```
|
||||
|
||||
#### Authorization Checks (CASL)
|
||||
```typescript
|
||||
// Controller with CASL guard
|
||||
@Post()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard, AbilitiesGuard)
|
||||
@CheckAbilities({ action: 'create', subject: 'Correspondence' })
|
||||
async create(@Body() dto: CreateCorrespondenceDto, @Request() req) {
|
||||
// Service logic
|
||||
}
|
||||
```
|
||||
|
||||
#### RBAC Matrix (ADR-016)
|
||||
- [ ] 4-Level RBAC matrix implemented (Admin, Manager, User, Viewer)
|
||||
- [ ] CASL AbilityFactory configured with correct permissions
|
||||
- [ ] JwtAuthGuard on all protected routes
|
||||
- [ ] RolesGuard for role-based access
|
||||
- [ ] AuditLogInterceptor on all mutation endpoints
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] Tokens stored in httpOnly cookies (not localStorage)
|
||||
- [ ] Authorization checks before sensitive operations
|
||||
- [ ] CASL abilities configured correctly
|
||||
- [ ] Role-based access control implemented
|
||||
- [ ] Session management secure
|
||||
|
||||
### 5. XSS Prevention
|
||||
|
||||
#### Sanitize HTML
|
||||
```typescript
|
||||
import DOMPurify from 'isomorphic-dompurify'
|
||||
|
||||
// ALWAYS sanitize user-provided HTML
|
||||
function renderUserContent(html: string) {
|
||||
const clean = DOMPurify.sanitize(html, {
|
||||
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
|
||||
ALLOWED_ATTR: []
|
||||
})
|
||||
return <div dangerouslySetInnerHTML={{ __html: clean }} />
|
||||
}
|
||||
```
|
||||
|
||||
#### Content Security Policy (Next.js)
|
||||
```typescript
|
||||
// next.config.js
|
||||
const securityHeaders = [
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: `
|
||||
default-src 'self';
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' data: https:;
|
||||
font-src 'self';
|
||||
connect-src 'self' http://localhost:3001 https://192.168.10.8;
|
||||
`.replace(/\s{2,}/g, ' ').trim()
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] User-provided HTML sanitized
|
||||
- [ ] CSP headers configured
|
||||
- [ ] No unvalidated dynamic content rendering
|
||||
- [ ] React's built-in XSS protection used
|
||||
|
||||
### 6. CSRF Protection
|
||||
|
||||
#### CSRF Tokens
|
||||
```typescript
|
||||
import { csrf } from '@/lib/csrf'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const token = request.headers.get('X-CSRF-Token')
|
||||
|
||||
if (!csrf.verify(token)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid CSRF token' },
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
// Process request
|
||||
}
|
||||
```
|
||||
|
||||
#### SameSite Cookies
|
||||
```typescript
|
||||
response.setHeader('Set-Cookie',
|
||||
`session=${sessionId}; HttpOnly; Secure; SameSite=Strict`
|
||||
)
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] CSRF tokens on state-changing operations
|
||||
- [ ] SameSite=Strict on all cookies
|
||||
- [ ] Double-submit cookie pattern implemented
|
||||
|
||||
### 7. Rate Limiting (ADR-016)
|
||||
|
||||
#### API Rate Limiting
|
||||
```typescript
|
||||
import { ThrottlerGuard } from '@nestjs/throttler'
|
||||
|
||||
// Apply to auth endpoints
|
||||
@UseGuards(ThrottlerGuard)
|
||||
@Throttle({ default: { limit: 10, ttl: 60000 } })
|
||||
async login(@Body() dto: LoginDto) {
|
||||
// Login logic
|
||||
}
|
||||
```
|
||||
|
||||
#### Expensive Operations
|
||||
```typescript
|
||||
// Aggressive rate limiting for AI endpoints
|
||||
@Throttle({ default: { limit: 5, ttl: 60000 } })
|
||||
async extractMetadata(@Body() dto: ExtractMetadataDto) {
|
||||
// AI extraction logic
|
||||
}
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] Rate limiting on all auth endpoints (ADR-016)
|
||||
- [ ] Rate limiting on AI endpoints (ADR-018/023)
|
||||
- [ ] IP-based rate limiting
|
||||
- [ ] User-based rate limiting (authenticated)
|
||||
|
||||
### 8. Sensitive Data Exposure
|
||||
|
||||
#### Logging
|
||||
```typescript
|
||||
// FAIL: WRONG: Logging sensitive data
|
||||
this.logger.log('User login:', { email, password })
|
||||
this.logger.log('Payment:', { cardNumber, cvv })
|
||||
|
||||
// PASS: CORRECT: Redact sensitive data
|
||||
this.logger.log('User login:', { email, userId })
|
||||
this.logger.log('Payment:', { last4: card.last4, userId })
|
||||
```
|
||||
|
||||
#### Error Messages (ADR-007)
|
||||
```typescript
|
||||
// FAIL: WRONG: Exposing internal details
|
||||
catch (error) {
|
||||
return { error: error.message, stack: error.stack }
|
||||
}
|
||||
|
||||
// PASS: CORRECT: Generic error messages
|
||||
catch (error) {
|
||||
this.logger.error('Internal error:', error)
|
||||
throw new BadRequestException('An error occurred. Please try again.')
|
||||
}
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] No passwords, tokens, or secrets in logs
|
||||
- [ ] Error messages generic for users
|
||||
- [ ] Detailed errors only in server logs
|
||||
- [ ] No stack traces exposed to users
|
||||
|
||||
### 9. AI Boundary Enforcement (ADR-018/023)
|
||||
|
||||
#### FAIL: NEVER Do This
|
||||
```typescript
|
||||
// Direct AI access - FORBIDDEN
|
||||
import ollama from 'ollama'
|
||||
const response = await ollama.chat({ model: 'gemma4', messages })
|
||||
|
||||
// Direct Qdrant access - FORBIDDEN
|
||||
import { QdrantClient } from '@qdrant/js-client-rest'
|
||||
const client = new QdrantClient({ url: 'http://localhost:6333' })
|
||||
```
|
||||
|
||||
#### PASS: ALWAYS Do This
|
||||
```typescript
|
||||
// AI via DMS API only
|
||||
const response = await fetch('http://localhost:3001/api/ai/extract-metadata', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ documentId })
|
||||
})
|
||||
|
||||
// Qdrant via DMS API only
|
||||
const response = await fetch('http://localhost:3001/api/ai/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, projectPublicId })
|
||||
})
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] AI processing on Admin Desktop only (Desk-5439)
|
||||
- [ ] No direct Ollama calls from backend/frontend
|
||||
- [ ] No direct Qdrant calls from backend/frontend
|
||||
- [ ] All AI interactions via DMS API endpoints
|
||||
- [ ] AI audit logging implemented (ADR-020)
|
||||
- [ ] Human-in-the-loop validation for AI outputs
|
||||
|
||||
### 10. UUID Handling (ADR-019)
|
||||
|
||||
#### FAIL: NEVER Do This
|
||||
```typescript
|
||||
// parseInt on UUID - FORBIDDEN
|
||||
const projectId = parseInt(projectUuid) // "0195..." → 19 (WRONG!)
|
||||
|
||||
// Number on UUID - FORBIDDEN
|
||||
const projectId = Number(projectUuid)
|
||||
|
||||
// + operator on UUID - FORBIDDEN
|
||||
const projectId = +projectUuid
|
||||
|
||||
// id ?? '' fallback - FORBIDDEN
|
||||
const value = c.publicId ?? c.id ?? ''
|
||||
```
|
||||
|
||||
#### PASS: ALWAYS Do This
|
||||
```typescript
|
||||
// Use UUID string directly
|
||||
const projectId = projectUuid // "019505a1-7c3e-7000-8000-abc123def456"
|
||||
|
||||
// Backend: findOneByUuid returns entity with publicId
|
||||
const project = await this.projectService.findOneByUuid(projectUuid)
|
||||
const projectId = project.id // Internal INT for DB operations
|
||||
|
||||
// Frontend: use publicId only
|
||||
interface ProjectOption {
|
||||
publicId?: string; // No uuid fallback
|
||||
projectName?: string;
|
||||
}
|
||||
const value = c.publicId // "019505a1-7c3e-7000-8000-abc123def456"
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] No `parseInt()` on UUID values
|
||||
- [ ] No `Number()` on UUID values
|
||||
- [ ] No `+` operator on UUID values
|
||||
- [ ] No `id ?? ''` fallback patterns
|
||||
- [ ] Use `publicId` (string UUID) in API responses
|
||||
- [ ] Internal INT `id` marked with `@Exclude()` in entities
|
||||
|
||||
### 11. Dependency Security
|
||||
|
||||
#### Regular Updates
|
||||
```bash
|
||||
# Check for vulnerabilities
|
||||
pnpm audit
|
||||
|
||||
# Fix automatically fixable issues
|
||||
pnpm audit fix
|
||||
|
||||
# Update dependencies
|
||||
pnpm update
|
||||
|
||||
# Check for outdated packages
|
||||
pnpm outdated
|
||||
```
|
||||
|
||||
#### Lock Files
|
||||
```bash
|
||||
# ALWAYS commit lock files
|
||||
git add pnpm-lock.yaml
|
||||
|
||||
# Use in CI/CD for reproducible builds
|
||||
pnpm install --frozen-lockfile
|
||||
```
|
||||
|
||||
#### Verification Steps
|
||||
- [ ] Dependencies up to date
|
||||
- [ ] No known vulnerabilities (pnpm audit clean)
|
||||
- [ ] Lock files committed
|
||||
- [ ] Regular security updates
|
||||
|
||||
## Security Testing
|
||||
|
||||
### Automated Security Tests
|
||||
|
||||
```typescript
|
||||
// Test authentication
|
||||
test('requires authentication', async () => {
|
||||
const response = await fetch('/api/correspondences')
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
|
||||
// Test authorization
|
||||
test('requires admin role', async () => {
|
||||
const response = await fetch('/api/admin/users', {
|
||||
headers: { Authorization: `Bearer ${userToken}` }
|
||||
})
|
||||
expect(response.status).toBe(403)
|
||||
})
|
||||
|
||||
// Test input validation
|
||||
test('rejects invalid input', async () => {
|
||||
const response = await fetch('/api/correspondences', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ subject: '', recipientId: 'invalid' })
|
||||
})
|
||||
expect(response.status).toBe(400)
|
||||
})
|
||||
|
||||
// Test rate limiting
|
||||
test('enforces rate limits', async () => {
|
||||
const requests = Array(11).fill(null).map(() =>
|
||||
fetch('/api/auth/login', { method: 'POST' })
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
const tooManyRequests = responses.filter(r => r.status === 429)
|
||||
|
||||
expect(tooManyRequests.length).toBeGreaterThan(0)
|
||||
})
|
||||
```
|
||||
|
||||
## Pre-Deployment Security Checklist
|
||||
|
||||
Before ANY production deployment:
|
||||
|
||||
- [ ] **Secrets**: No hardcoded secrets, all in env vars
|
||||
- [ ] **Input Validation**: All user inputs validated (Zod + class-validator)
|
||||
- [ ] **SQL Injection**: All queries parameterized (TypeORM)
|
||||
- [ ] **XSS**: User content sanitized
|
||||
- [ ] **CSRF**: Protection enabled
|
||||
- [ ] **Authentication**: Proper token handling (httpOnly cookies)
|
||||
- [ ] **Authorization**: RBAC + CASL checks in place
|
||||
- [ ] **Rate Limiting**: Enabled on auth and AI endpoints
|
||||
- [ ] **HTTPS**: Enforced in production
|
||||
- [ ] **Security Headers**: CSP, X-Frame-Options configured
|
||||
- [ ] **Error Handling**: No sensitive data in errors (ADR-007)
|
||||
- [ ] **Logging**: No sensitive data logged
|
||||
- [ ] **Dependencies**: Up to date, no vulnerabilities
|
||||
- [ ] **UUID Handling**: No parseInt/Number/+ on UUID (ADR-019)
|
||||
- [ ] **AI Boundary**: AI via DMS API only (ADR-018/023)
|
||||
- [ ] **File Uploads**: Validated (50MB max, whitelist types)
|
||||
- [ ] **AI Audit**: All AI interactions logged (ADR-020)
|
||||
|
||||
## Resources
|
||||
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [NestJS Security](https://docs.nestjs.com/security)
|
||||
- [Next.js Security](https://nextjs.org/docs/security)
|
||||
- [ADR-016 Security Authentication](../../specs/06-Decision-Records/ADR-016-security-authentication.md)
|
||||
- [ADR-018 AI Boundary](../../specs/06-Decision-Records/ADR-018-ai-boundary.md)
|
||||
- [ADR-019 UUID Strategy](../../specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md)
|
||||
- [ADR-023 AI Architecture](../../specs/06-Decision-Records/ADR-023-unified-ai-architecture.md)
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Security is not optional. One vulnerability can compromise the entire platform. When in doubt, err on the side of caution.
|
||||
@@ -1,8 +1,8 @@
|
||||
# 🧠 NAP-DMS Agent Skills (v1.8.9)
|
||||
# 🧠 NAP-DMS Agent Skills (v1.9.0)
|
||||
|
||||
ไฟล์นี้กำหนดทักษะและความสามารถเฉพาะทางของ Document Intelligence Engine สำหรับโครงการ LCBP3 v1.8.9 เพื่อรักษามาตรฐานสูงสุดด้าน Security และ Data Integrity
|
||||
ไฟล์นี้กำหนดทักษะและความสามารถเฉพาะทางของ Document Intelligence Engine สำหรับโครงการ LCBP3 v1.9.0 เพื่อรักษามาตรฐานสูงสุดด้าน Security และ Data Integrity
|
||||
|
||||
**Status**: Production Ready | **Last Updated**: 2026-04-22 | **Total Skills**: 20
|
||||
**Status**: Production Ready | **Last Updated**: 2026-05-17 | **Total Skills**: 23
|
||||
|
||||
> 📌 Shared context for all speckit-\* skills: see [`_LCBP3-CONTEXT.md`](./_LCBP3-CONTEXT.md).
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
## 🔄 Skill Dependency Matrix
|
||||
|
||||
| Skill | Dependencies | Handoffs To | Notes |
|
||||
| -------------------------- | -------------------- | -------------------------------- | ----------------------------- |
|
||||
| -------------------------- | -------------------- | ---------------------------------------- | ----------------------------- |
|
||||
| **speckit-constitution** | None | speckit-specify | Project governance foundation |
|
||||
| **speckit-specify** | speckit-constitution | speckit-clarify | Feature specification |
|
||||
| **speckit-clarify** | speckit-specify | speckit-plan | Resolve ambiguities |
|
||||
@@ -79,6 +79,9 @@
|
||||
| **nestjs-best-practices** | None | speckit-implement | Backend patterns |
|
||||
| **next-best-practices** | None | speckit-implement | Frontend patterns |
|
||||
| **speckit-security-audit** | None | speckit-reviewer | Security validation |
|
||||
| **e2e-testing** | None | speckit-tester | Playwright E2E patterns |
|
||||
| **verification-loop** | None | speckit-checker, speckit-tester | Comprehensive verification |
|
||||
| **security-review** | None | speckit-reviewer, speckit-security-audit | OWASP Top 10 + ADR compliance |
|
||||
|
||||
---
|
||||
|
||||
@@ -96,8 +99,8 @@
|
||||
|
||||
### Health Metrics
|
||||
|
||||
- **Total Skills**: 20 implemented
|
||||
- **Version Alignment**: v1.8.9 across all skills
|
||||
- **Total Skills**: 23 implemented
|
||||
- **Version Alignment**: v1.9.0 across all skills
|
||||
- **Template Coverage**: 100% for skills requiring templates
|
||||
- **Documentation**: Complete front matter + shared `_LCBP3-CONTEXT.md` appendix
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
name: verification-loop
|
||||
description: A comprehensive verification system for LCBP3-DMS development sessions with build, type check, lint, test, security scan, and diff review phases.
|
||||
version: 1.9.0
|
||||
scope: verification
|
||||
depends-on: []
|
||||
handoffs-to: [speckit-checker, speckit-tester]
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Verification Loop Skill
|
||||
|
||||
A comprehensive verification system for LCBP3-DMS development sessions.
|
||||
|
||||
## LCBP3 Context
|
||||
|
||||
See [`_LCBP3-CONTEXT.md`](../_LCBP3-CONTEXT.md) for project-specific verification requirements:
|
||||
- Backend: NestJS with TypeScript strict mode
|
||||
- Frontend: Next.js with TypeScript strict mode
|
||||
- Package manager: pnpm
|
||||
- Coverage goals: Backend 70%+, Business Logic 80%+
|
||||
- Security: ADR-016, ADR-018, ADR-019, ADR-023 compliance
|
||||
|
||||
## When to Use
|
||||
|
||||
Invoke this skill:
|
||||
- After completing a feature or significant code change
|
||||
- Before creating a PR
|
||||
- When you want to ensure quality gates pass
|
||||
- After refactoring
|
||||
- Before deploying to staging/production
|
||||
|
||||
## Verification Phases
|
||||
|
||||
### Phase 1: Build Verification
|
||||
|
||||
```bash
|
||||
# Backend build
|
||||
cd backend
|
||||
pnpm build 2>&1 | tail -20
|
||||
|
||||
# Frontend build
|
||||
cd frontend
|
||||
pnpm build 2>&1 | tail -20
|
||||
```
|
||||
|
||||
If build fails, STOP and fix before continuing.
|
||||
|
||||
### Phase 2: Type Check
|
||||
|
||||
```bash
|
||||
# Backend TypeScript
|
||||
cd backend
|
||||
pnpm typecheck 2>&1 | head -30
|
||||
|
||||
# Frontend TypeScript
|
||||
cd frontend
|
||||
pnpm typecheck 2>&1 | head -30
|
||||
```
|
||||
|
||||
Report all type errors. Fix critical ones before continuing.
|
||||
|
||||
### Phase 3: Lint Check
|
||||
|
||||
```bash
|
||||
# Backend lint
|
||||
cd backend
|
||||
pnpm lint 2>&1 | head -30
|
||||
|
||||
# Frontend lint
|
||||
cd frontend
|
||||
pnpm lint 2>&1 | head -30
|
||||
```
|
||||
|
||||
### Phase 4: Test Suite
|
||||
|
||||
```bash
|
||||
# Backend tests with coverage
|
||||
cd backend
|
||||
pnpm test -- --coverage 2>&1 | tail -50
|
||||
|
||||
# Frontend unit tests
|
||||
cd frontend
|
||||
pnpm test 2>&1 | tail -50
|
||||
|
||||
# Frontend E2E tests (if applicable)
|
||||
cd frontend
|
||||
npx playwright test 2>&1 | tail -50
|
||||
```
|
||||
|
||||
Report:
|
||||
- Total tests: X
|
||||
- Passed: X
|
||||
- Failed: X
|
||||
- Coverage: X%
|
||||
|
||||
### Phase 5: Security Scan
|
||||
|
||||
```bash
|
||||
# Check for hardcoded secrets
|
||||
grep -rn "sk-" --include="*.ts" --include="*.tsx" . 2>/dev/null | head -10
|
||||
grep -rn "api_key" --include="*.ts" --include="*.tsx" . 2>/dev/null | head -10
|
||||
grep -rn "password" --include="*.ts" --include="*.tsx" . 2>/dev/null | head -10
|
||||
|
||||
# Check for console.log (forbidden in committed code)
|
||||
grep -rn "console.log" --include="*.ts" --include="*.tsx" backend/src/ frontend/src/ 2>/dev/null | head -10
|
||||
|
||||
# Check for any types (forbidden)
|
||||
grep -rn ": any" --include="*.ts" --include="*.tsx" backend/src/ frontend/src/ 2>/dev/null | head -10
|
||||
|
||||
# Check for parseInt on UUID (ADR-019 violation)
|
||||
grep -rn "parseInt(" --include="*.ts" --include="*.tsx" backend/src/ frontend/src/ 2>/dev/null | head -10
|
||||
```
|
||||
|
||||
### Phase 6: ADR Compliance Check
|
||||
|
||||
```bash
|
||||
# Check for id ?? '' fallback (ADR-019 violation)
|
||||
grep -rn "id ?? ''" --include="*.ts" --include="*.tsx" frontend/src/ 2>/dev/null | head -10
|
||||
|
||||
# Check for Number() on UUID (ADR-019 violation)
|
||||
grep -rn "Number(" --include="*.ts" --include="*.tsx" frontend/src/ 2>/dev/null | head -10
|
||||
|
||||
# Check for + operator on UUID (ADR-019 violation)
|
||||
grep -rn "+ publicId\|+ id" --include="*.ts" --include="*.tsx" frontend/src/ 2>/dev/null | head -10
|
||||
```
|
||||
|
||||
### Phase 7: Diff Review
|
||||
|
||||
```bash
|
||||
# Show what changed
|
||||
git diff --stat
|
||||
git diff HEAD~1 --name-only
|
||||
|
||||
# Show detailed changes
|
||||
git diff
|
||||
```
|
||||
|
||||
Review each changed file for:
|
||||
- Unintended changes
|
||||
- Missing error handling (ADR-007)
|
||||
- Potential edge cases
|
||||
- UUID handling (ADR-019)
|
||||
- Security vulnerabilities (ADR-016)
|
||||
- AI boundary violations (ADR-018/023)
|
||||
|
||||
## Output Format
|
||||
|
||||
After running all phases, produce a verification report:
|
||||
|
||||
```
|
||||
VERIFICATION REPORT
|
||||
==================
|
||||
|
||||
Build: [PASS/FAIL]
|
||||
Types: [PASS/FAIL] (X errors)
|
||||
Lint: [PASS/FAIL] (X warnings)
|
||||
Tests: [PASS/FAIL] (X/Y passed, Z% coverage)
|
||||
Security: [PASS/FAIL] (X issues)
|
||||
ADR: [PASS/FAIL] (X violations)
|
||||
Diff: [X files changed]
|
||||
|
||||
Overall: [READY/NOT READY] for PR
|
||||
|
||||
Issues to Fix:
|
||||
1. ...
|
||||
2. ...
|
||||
```
|
||||
|
||||
## Continuous Mode
|
||||
|
||||
For long sessions, run verification every 15 minutes or after major changes:
|
||||
|
||||
```markdown
|
||||
Set a mental checkpoint:
|
||||
- After completing each function
|
||||
- After finishing a component
|
||||
- Before moving to next task
|
||||
|
||||
Run: /verify
|
||||
```
|
||||
|
||||
## Integration with LCBP3 Skills
|
||||
|
||||
This skill complements:
|
||||
- **speckit-checker**: Runs static analysis (lint, typecheck)
|
||||
- **speckit-tester**: Runs tests with coverage verification
|
||||
- **speckit-security-audit**: Performs security review against OWASP Top 10
|
||||
|
||||
This skill provides a unified verification loop that combines all checks into a single report.
|
||||
|
||||
## LCBP3-Specific Checks
|
||||
|
||||
### Tier 1 — CRITICAL (CI BLOCKER)
|
||||
|
||||
- [ ] **Security**: Auth, RBAC, Validation implemented
|
||||
- [ ] **UUID Strategy (ADR-019)**: No `parseInt` / `Number` / `+` on UUID
|
||||
- [ ] **Database correctness**: Schema verified before writing queries
|
||||
- [ ] **File upload security**: ClamAV + whitelist implemented
|
||||
- [ ] **AI validation boundary (ADR-018/023)**: AI via DMS API only
|
||||
- [ ] **Error handling (ADR-007)**: Layered error classification
|
||||
- [ ] **Forbidden patterns**: Zero `any`, zero `console.log`, UUID misuse
|
||||
|
||||
### Tier 2 — IMPORTANT (CODE REVIEW)
|
||||
|
||||
- [ ] **Architecture patterns**: Thin controller, business logic in service
|
||||
- [ ] **Test coverage**: 80%+ business logic, 70%+ backend overall
|
||||
- [ ] **Cache invalidation**: Implemented when data modified
|
||||
- [ ] **Naming conventions**: Follow domain terminology
|
||||
|
||||
### Tier 3 — GUIDELINES
|
||||
|
||||
- [ ] **Code style**: Prettier formatting
|
||||
- [ ] **Comment completeness**: Thai comments, JSDoc on public methods
|
||||
- [ ] **Minor optimizations**: Performance improvements where applicable
|
||||
|
||||
## References
|
||||
|
||||
- LCBP3 AGENTS.md: `AGENTS.md` (repo root)
|
||||
- ADR-007 Error Handling: `specs/06-Decision-Records/ADR-007-error-handling-strategy.md`
|
||||
- ADR-016 Security: `specs/06-Decision-Records/ADR-016-security-authentication.md`
|
||||
- ADR-019 UUID: `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md`
|
||||
- ADR-018 AI Boundary: `specs/06-Decision-Records/ADR-018-ai-boundary.md`
|
||||
- ADR-023 AI Architecture: `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md`
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
auto_execution_mode: 0
|
||||
description: Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies for LCBP3-DMS
|
||||
---
|
||||
|
||||
This workflow invokes the e2e-testing skill to help with Playwright E2E testing patterns for LCBP3-DMS.
|
||||
|
||||
Invoke the e2e-testing skill when:
|
||||
- Creating new E2E tests for frontend features
|
||||
- Debugging flaky Playwright tests
|
||||
- Setting up CI/CD integration for E2E tests
|
||||
- Optimizing test performance and reliability
|
||||
- Implementing Page Object Model (POM) patterns
|
||||
@@ -40,7 +40,7 @@ The following are **CI-blocking issues** that must be caught in code review. The
|
||||
|
||||
- **❌ NO SQL Triggers for business logic** — use NestJS Service methods instead
|
||||
- **❌ NO `.env` files in production** — use Docker environment variables
|
||||
- **❌ NO direct table/column name invention** — verify against `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql`
|
||||
- **❌ NO direct table/column name invention** — verify against `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql`
|
||||
|
||||
### Security (ADR-016)
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
auto_execution_mode: 0
|
||||
description: Comprehensive security review for LCBP3-DMS with OWASP Top 10 checklist, ADR compliance, and automated security testing patterns
|
||||
---
|
||||
|
||||
This workflow invokes the security-review skill to perform comprehensive security review of LCBP3-DMS code changes.
|
||||
|
||||
Invoke the security-review skill when:
|
||||
- Implementing authentication or authorization
|
||||
- Handling user input or file uploads
|
||||
- Creating new API endpoints
|
||||
- Working with secrets or credentials
|
||||
- Integrating AI features (Ollama/Qdrant)
|
||||
- Storing or transmitting sensitive data
|
||||
- Integrating third-party APIs
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
auto_execution_mode: 0
|
||||
description: A comprehensive verification system for LCBP3-DMS development sessions with build, type check, lint, test, security scan, and diff review phases
|
||||
---
|
||||
|
||||
This workflow invokes the verification-loop skill to perform comprehensive verification of LCBP3-DMS code changes.
|
||||
|
||||
Invoke the verification-loop skill when:
|
||||
- After completing a feature or significant code change
|
||||
- Before creating a PR
|
||||
- When you want to ensure quality gates pass
|
||||
- After refactoring
|
||||
- Before deploying to staging/production
|
||||
@@ -112,7 +112,7 @@ Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > oth
|
||||
| Document | Path | Status | Use When |
|
||||
| ---------------------------- | -------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------- |
|
||||
| **Glossary** | `specs/00-overview/00-02-glossary.md` | — | Verify domain terminology |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | — | Before writing any query |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql` | — | Before writing any query |
|
||||
| **Data Dictionary** | `specs/03-Data-and-Storage/03-01-data-dictionary.md` | — | Field meanings + business rules |
|
||||
| **RBAC Matrix** | `specs/01-requirements/01-02-business-rules/01-02-01-rbac-matrix.md` | — | Permission levels + roles |
|
||||
| **Edge Cases** | `specs/01-Requirements/01-06-edge-cases-and-rules.md` | — | Prevent bugs in flows |
|
||||
@@ -122,6 +122,7 @@ Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > oth
|
||||
| **ADR-008 Notifications** | `specs/06-Decision-Records/ADR-008-email-notification-strategy.md` | ✅ Active | BullMQ + multi-channel notification |
|
||||
| **ADR-009 DB Migration** | `specs/06-Decision-Records/ADR-009-database-migration-strategy.md` | ✅ Active | Schema changes — edit SQL directly |
|
||||
| **ADR-016 Security** | `specs/06-Decision-Records/ADR-016-security-authentication.md` | ✅ Active | Auth, RBAC, file upload security |
|
||||
| **ADR-015 Release Strategy** | `specs/06-Decision-Records/ADR-015-deployment-infrastructure.md` | ✅ Active | Blue-Green deployment + release gates |
|
||||
| **ADR-019 UUID** | `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | ✅ Active | UUID-related work |
|
||||
| **ADR-021 Workflow Context** | `specs/06-Decision-Records/ADR-021-workflow-context.md` | ✅ Active | Integrated workflow & step attachments |
|
||||
| **ADR-023 AI Architecture** | `specs/06-Decision-Records/ADR-023-unified-ai-architecture.md` | ✅ Active | Unified AI boundaries and pipeline (base architecture) |
|
||||
@@ -487,6 +488,7 @@ This file is a **quick reference**. For detailed information:
|
||||
|
||||
| Version | Date | Changes | Updated By |
|
||||
| ------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
|
||||
| 1.9.4 | 2026-05-16 | Added ADR-015 Release Strategy to Key Spec Files table (Blue-Green deployment + release gates) | Human Dev |
|
||||
| 1.9.3 | 2026-05-15 | ADR-023A: Model revision — gemma4:9b+Typhoon→gemma4:e4b Q8_0 (2-model stack), BullMQ 2-queue split, RAG full-doc embed, OCR auto-detect, n8n→DMS API boundary, QdrantService multi-tenancy contract | Windsurf AI |
|
||||
| 1.9.2 | 2026-05-14 | Consolidated legacy AI ADRs (017, 017B, 018, 020, 022) into master ADR-023: Unified AI Architecture | Antigravity AI |
|
||||
| 1.9.1 | 2026-05-13 | Added `bugfix` workflow and skill (migrated and improved from `docs/bugfix.md`) | Windsurf AI |
|
||||
|
||||
+2
-2
@@ -97,7 +97,7 @@ specs/
|
||||
### 📋 หมวดหมู่เอกสาร
|
||||
|
||||
| หมวด | วัตถุประสงค์ | ไฟล์สำคัญ | ผู้ดูแล |
|
||||
| ----------------------------- | -------------------------------------- | ------------------- | ----------------------- |
|
||||
| ----------------------------- | -------------------------------------- | ---------------- | ----------------------- |
|
||||
| **00-Overview** | ภาพรวม, Product Vision, KPI, Training | Gap 1/5/6/9 | Project Manager / PO |
|
||||
| **01-Requirements** | User Stories, UAT, UI, Edge Cases | Gap 2/3/4/10 | Business Analyst + PO |
|
||||
| **02-Architecture** | สถาปัตยกรรมและการออกแบบ | — | Tech Lead + Architects |
|
||||
@@ -754,7 +754,7 @@ bash ./.agents/scripts/bash/audit-skills.sh
|
||||
### 🔴 Tier 1 Non-Negotiables (AI must enforce)
|
||||
|
||||
- **ADR-019 UUID** — `publicId` exposed directly; ห้าม `parseInt`/`Number`/`+` บน UUID; ห้าม `id ?? ''` fallback; ห้ามใช้ `@Expose({ name: 'id' })` rename
|
||||
- **ADR-009 Schema** — แก้ `lcbp3-v1.8.0-schema-02-tables.sql` โดยตรง + เพิ่ม delta ที่ `specs/03-Data-and-Storage/deltas/`; ห้าม TypeORM migrations
|
||||
- **ADR-009 Schema** — แก้ `lcbp3-v1.9.0-schema-02-tables.sql` โดยตรง + เพิ่ม delta ที่ `specs/03-Data-and-Storage/deltas/`; ห้าม TypeORM migrations
|
||||
- **ADR-016 Security** — CASL + `Idempotency-Key` + ClamAV two-phase upload
|
||||
- **ADR-023 AI Architecture** — Ollama on Admin Desktop only; human-in-the-loop validation
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
"example": "examples"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "pnpm -r test",
|
||||
"start:mcp": "node ./scripts/start-mcp.js",
|
||||
"dev:backend": "pnpm --filter backend start:dev",
|
||||
"dev:frontend": "pnpm --filter lcbp3-frontend dev",
|
||||
|
||||
Generated
+379
-10
@@ -62,6 +62,12 @@ importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@commitlint/cli':
|
||||
specifier: ^21.0.1
|
||||
version: 21.0.1(@types/node@25.5.0)(conventional-commits-parser@6.4.0)(typescript@5.9.3)
|
||||
'@commitlint/config-conventional':
|
||||
specifier: ^21.0.1
|
||||
version: 21.0.1
|
||||
husky:
|
||||
specifier: ^9.1.7
|
||||
version: 9.1.7
|
||||
@@ -1393,6 +1399,75 @@ packages:
|
||||
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
||||
engines: {node: '>=0.1.90'}
|
||||
|
||||
'@commitlint/cli@21.0.1':
|
||||
resolution: {integrity: sha512-8vq10krmbJwBkvzXKhbs4o4JQEVscd3pqOlWuDUaDBwbeL694/P33UC29tZQFTAgPU9fVJ2+f2m3zw16yKWxHg==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
hasBin: true
|
||||
|
||||
'@commitlint/config-conventional@21.0.1':
|
||||
resolution: {integrity: sha512-gRorrkfWOh/+V5X8GYWWbQvrzPczopGMS4CCNrQdHkK4xWElv82BDvIsDhJZWTlI7TazOlYea6VATufCsFs+sw==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/config-validator@21.0.1':
|
||||
resolution: {integrity: sha512-Zd2UFdndeMMaW2O96HK0tdfT4gOImUvidMpAd/pws2zZ4m1nrAZ/9b/v2JYuE8fs86GpXv9F7LNaIuCIWhY+pA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/ensure@21.0.1':
|
||||
resolution: {integrity: sha512-jJ1037967wU7YN/xkv+iRlOBlmaOXPhPO5KQSqya6GyXzBlwuLzELBFao16DVg9dZyqmNrhewzwZ3SAibetHBQ==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/execute-rule@21.0.1':
|
||||
resolution: {integrity: sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/format@21.0.1':
|
||||
resolution: {integrity: sha512-ksmG2+cHGtuDPQQbhBbC4unwm444+6TiPw0d1bKf67hntgZqZ8E0g1MuYKUuyT5IH4IMmXZhKq22/Z3jBvtQIw==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/is-ignored@21.0.1':
|
||||
resolution: {integrity: sha512-iNDP8SFdw8JEkM0CHZ2XFnhTN4Zg5jKUY2d8kBOSFrI2aA+3YJI7fcqVpfgbpJ9xtxFVYpi+DBATU5AvhoTq8g==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/lint@21.0.1':
|
||||
resolution: {integrity: sha512-gF+iYtUw1gBG3HUH9z3VxwUjGg2R2G5j+nmvPs8aIeYkiB7TtneBu3wO85I0bUl93bYNsvsCNI9Nte2fmDUMww==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/load@21.0.1':
|
||||
resolution: {integrity: sha512-Btg1q1mKmiihN4W3x0EsPDrJMOQfMa9NIqlzlJyXAfxvsOGdGXOW5p3R3RcSxDCaY7JabY9flIl+Om1af3PSrw==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/message@21.0.1':
|
||||
resolution: {integrity: sha512-R3dVQeJQ0B6yqrZEjkUHD4r7UJYLV9Lvk2xs3PTOmtWk2G3mI6Xgc+YdRxL1PwcDfBiUjv2SkIkW4AUc976w1w==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/parse@21.0.1':
|
||||
resolution: {integrity: sha512-oh/nCSOqdoeQNA1tO8aAmxkq5EBo8/NzcFQRvv66AWc9HpED28sL2iSicCKU6hPintWuscL6BJEWi77Wq1LPMQ==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/read@21.0.1':
|
||||
resolution: {integrity: sha512-pMEu4lbpC8W0ZgKJj2U6WaobXIZWdFlULpIEewYhkPXx+WZcnoO53YrVPc7QErQuNolq2Me8dP58Wu7YAVXVOA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/resolve-extends@21.0.1':
|
||||
resolution: {integrity: sha512-0DhjYWL6uYrY16Efa032fYk3woGJDU4AGWiG1XXltT9AMUNYKyb5cIZU2ivbaMZ3+kKFqUjikD2cjh66Sbh/Sg==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/rules@21.0.1':
|
||||
resolution: {integrity: sha512-VMooYpz4nJg7xlaUso6CCOWEz8D/ChkvsvZUMARcoJ1ZpfKPyFCGrHNha2tbsETNAb6ErgiRuCr2DvghrvPDYQ==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/to-lines@21.0.1':
|
||||
resolution: {integrity: sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/top-level@21.0.1':
|
||||
resolution: {integrity: sha512-4esUYqzY7K0FCgcJ/1xWEZekV7Ch4yZT1+xjEb7KzqbJ05XEkxHVsTfC8ADKNNtlCE2pj98KEbPGZWw9WwEnVw==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@commitlint/types@21.0.1':
|
||||
resolution: {integrity: sha512-4u7w8jcoCUFWhjWnASYzZHAP34OqOtuFBN87nQmFvqda03YU0T6z+yB4w0gSAMpekiRqqGk5rt+qSlW+a2vSEg==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@compodoc/compodoc@1.2.1':
|
||||
resolution: {integrity: sha512-won7I0OeFM0zSi+cwVilwtLA7VpY9NzsBch6+gGElYQdbM4/01XgA//leF8EsNzhxuQb+kP86MqJ3loZhz+Big==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
@@ -1411,6 +1486,18 @@ packages:
|
||||
resolution: {integrity: sha512-oWxJza7CpWR8/FeWYfE6j+jgncnGBsTWnZLt5rD2GUpsGSQTuGrsFPnmbbaVLgRS5QIVWBJYke7QFBr/7qVMWg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@conventional-changelog/git-client@2.7.0':
|
||||
resolution: {integrity: sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
conventional-commits-filter: ^5.0.0
|
||||
conventional-commits-parser: ^6.4.0
|
||||
peerDependenciesMeta:
|
||||
conventional-commits-filter:
|
||||
optional: true
|
||||
conventional-commits-parser:
|
||||
optional: true
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3301,6 +3388,14 @@ packages:
|
||||
'@scarf/scarf@1.4.0':
|
||||
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
|
||||
|
||||
'@simple-libs/child-process-utils@1.0.2':
|
||||
resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@simple-libs/stream-utils@1.2.0':
|
||||
resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sinclair/typebox@0.34.41':
|
||||
resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==}
|
||||
|
||||
@@ -4308,6 +4403,9 @@ packages:
|
||||
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
array-ify@1.0.0:
|
||||
resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
|
||||
|
||||
array-includes@3.1.9:
|
||||
resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4688,6 +4786,10 @@ packages:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
cliui@9.0.1:
|
||||
resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
clone@1.0.4:
|
||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -4773,6 +4875,9 @@ packages:
|
||||
resolution: {integrity: sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
compare-func@2.0.0:
|
||||
resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
|
||||
|
||||
component-emitter@1.3.1:
|
||||
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
|
||||
|
||||
@@ -4800,6 +4905,19 @@ packages:
|
||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
conventional-changelog-angular@8.3.1:
|
||||
resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
conventional-changelog-conventionalcommits@9.3.1:
|
||||
resolution: {integrity: sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
conventional-commits-parser@6.4.0:
|
||||
resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
@@ -4828,6 +4946,14 @@ packages:
|
||||
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
cosmiconfig-typescript-loader@6.3.0:
|
||||
resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==}
|
||||
engines: {node: '>=v18'}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
cosmiconfig: '>=9'
|
||||
typescript: '>=5'
|
||||
|
||||
cosmiconfig@8.3.6:
|
||||
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -4846,6 +4972,15 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
cosmiconfig@9.0.1:
|
||||
resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
typescript: '>=4.9.5'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
create-require@1.1.1:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
|
||||
@@ -5089,6 +5224,10 @@ packages:
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
|
||||
dot-prop@5.3.0:
|
||||
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dot@2.0.0-beta.1:
|
||||
resolution: {integrity: sha512-kxM7fSnNQTXOmaeGuBSXM8O3fEsBb7XSDBllkGbRwa0lJSJTxxDE/4eSNGLKZUmlFw0f1vJ5qSV2BljrgQtgIA==}
|
||||
|
||||
@@ -5223,6 +5362,9 @@ packages:
|
||||
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-toolkit@1.46.1:
|
||||
resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==}
|
||||
|
||||
es6-shim@0.35.8:
|
||||
resolution: {integrity: sha512-Twf7I2v4/1tLoIXMT8HlqaBSS5H2wQTs2wx3MNYCI8K1R1/clXyCazrcVCPm/FuO9cyV8+leEaZOWD5C253NDg==}
|
||||
|
||||
@@ -5651,10 +5793,6 @@ packages:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-east-asian-width@1.4.0:
|
||||
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
get-east-asian-width@1.5.0:
|
||||
resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5694,6 +5832,11 @@ packages:
|
||||
get-tsconfig@4.13.6:
|
||||
resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
|
||||
|
||||
git-raw-commits@5.0.1:
|
||||
resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -5722,6 +5865,10 @@ packages:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
global-directory@5.0.0:
|
||||
resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
globals@14.0.0:
|
||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5913,6 +6060,10 @@ packages:
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ini@6.0.0:
|
||||
resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
|
||||
internal-slot@1.1.0:
|
||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -6028,6 +6179,14 @@ packages:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
is-obj@2.0.0:
|
||||
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-plain-obj@4.1.0:
|
||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-potential-custom-element-name@1.0.1:
|
||||
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||
|
||||
@@ -6659,6 +6818,10 @@ packages:
|
||||
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
||||
meow@13.2.0:
|
||||
resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
merge-descriptors@2.0.0:
|
||||
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -8606,10 +8769,18 @@ packages:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs-parser@22.0.0:
|
||||
resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
|
||||
|
||||
yargs@17.7.2:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@18.0.0:
|
||||
resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
|
||||
|
||||
yn@3.1.1:
|
||||
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -9994,6 +10165,114 @@ snapshots:
|
||||
|
||||
'@colors/colors@1.6.0': {}
|
||||
|
||||
'@commitlint/cli@21.0.1(@types/node@25.5.0)(conventional-commits-parser@6.4.0)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@commitlint/format': 21.0.1
|
||||
'@commitlint/lint': 21.0.1
|
||||
'@commitlint/load': 21.0.1(@types/node@25.5.0)(typescript@5.9.3)
|
||||
'@commitlint/read': 21.0.1(conventional-commits-parser@6.4.0)
|
||||
'@commitlint/types': 21.0.1
|
||||
tinyexec: 1.0.4
|
||||
yargs: 18.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- conventional-commits-filter
|
||||
- conventional-commits-parser
|
||||
- typescript
|
||||
|
||||
'@commitlint/config-conventional@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/types': 21.0.1
|
||||
conventional-changelog-conventionalcommits: 9.3.1
|
||||
|
||||
'@commitlint/config-validator@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/types': 21.0.1
|
||||
ajv: 8.18.0
|
||||
|
||||
'@commitlint/ensure@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/types': 21.0.1
|
||||
es-toolkit: 1.46.1
|
||||
|
||||
'@commitlint/execute-rule@21.0.1': {}
|
||||
|
||||
'@commitlint/format@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/types': 21.0.1
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@commitlint/is-ignored@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/types': 21.0.1
|
||||
semver: 7.7.4
|
||||
|
||||
'@commitlint/lint@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/is-ignored': 21.0.1
|
||||
'@commitlint/parse': 21.0.1
|
||||
'@commitlint/rules': 21.0.1
|
||||
'@commitlint/types': 21.0.1
|
||||
|
||||
'@commitlint/load@21.0.1(@types/node@25.5.0)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@commitlint/config-validator': 21.0.1
|
||||
'@commitlint/execute-rule': 21.0.1
|
||||
'@commitlint/resolve-extends': 21.0.1
|
||||
'@commitlint/types': 21.0.1
|
||||
cosmiconfig: 9.0.1(typescript@5.9.3)
|
||||
cosmiconfig-typescript-loader: 6.3.0(@types/node@25.5.0)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3)
|
||||
es-toolkit: 1.46.1
|
||||
is-plain-obj: 4.1.0
|
||||
picocolors: 1.1.1
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- typescript
|
||||
|
||||
'@commitlint/message@21.0.1': {}
|
||||
|
||||
'@commitlint/parse@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/types': 21.0.1
|
||||
conventional-changelog-angular: 8.3.1
|
||||
conventional-commits-parser: 6.4.0
|
||||
|
||||
'@commitlint/read@21.0.1(conventional-commits-parser@6.4.0)':
|
||||
dependencies:
|
||||
'@commitlint/top-level': 21.0.1
|
||||
'@commitlint/types': 21.0.1
|
||||
git-raw-commits: 5.0.1(conventional-commits-parser@6.4.0)
|
||||
tinyexec: 1.0.4
|
||||
transitivePeerDependencies:
|
||||
- conventional-commits-filter
|
||||
- conventional-commits-parser
|
||||
|
||||
'@commitlint/resolve-extends@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/config-validator': 21.0.1
|
||||
'@commitlint/types': 21.0.1
|
||||
es-toolkit: 1.46.1
|
||||
global-directory: 5.0.0
|
||||
resolve-from: 5.0.0
|
||||
|
||||
'@commitlint/rules@21.0.1':
|
||||
dependencies:
|
||||
'@commitlint/ensure': 21.0.1
|
||||
'@commitlint/message': 21.0.1
|
||||
'@commitlint/to-lines': 21.0.1
|
||||
'@commitlint/types': 21.0.1
|
||||
|
||||
'@commitlint/to-lines@21.0.1': {}
|
||||
|
||||
'@commitlint/top-level@21.0.1':
|
||||
dependencies:
|
||||
escalade: 3.2.0
|
||||
|
||||
'@commitlint/types@21.0.1':
|
||||
dependencies:
|
||||
conventional-commits-parser: 6.4.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@compodoc/compodoc@1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.4.0)(typescript@5.9.3)(vis-data@8.0.3(uuid@13.0.2)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1))':
|
||||
dependencies:
|
||||
'@angular-devkit/schematics': 21.1.0(chokidar@5.0.0)
|
||||
@@ -10080,6 +10359,14 @@ snapshots:
|
||||
dot: 2.0.0-beta.1
|
||||
fs-extra: 11.3.4
|
||||
|
||||
'@conventional-changelog/git-client@2.7.0(conventional-commits-parser@6.4.0)':
|
||||
dependencies:
|
||||
'@simple-libs/child-process-utils': 1.0.2
|
||||
'@simple-libs/stream-utils': 1.2.0
|
||||
semver: 7.7.4
|
||||
optionalDependencies:
|
||||
conventional-commits-parser: 6.4.0
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
@@ -11865,6 +12152,12 @@ snapshots:
|
||||
|
||||
'@scarf/scarf@1.4.0': {}
|
||||
|
||||
'@simple-libs/child-process-utils@1.0.2':
|
||||
dependencies:
|
||||
'@simple-libs/stream-utils': 1.2.0
|
||||
|
||||
'@simple-libs/stream-utils@1.2.0': {}
|
||||
|
||||
'@sinclair/typebox@0.34.41': {}
|
||||
|
||||
'@sinonjs/commons@3.0.1':
|
||||
@@ -13053,6 +13346,8 @@ snapshots:
|
||||
call-bound: 1.0.4
|
||||
is-array-buffer: 3.0.5
|
||||
|
||||
array-ify@1.0.0: {}
|
||||
|
||||
array-includes@3.1.9:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
@@ -13535,6 +13830,12 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
cliui@9.0.1:
|
||||
dependencies:
|
||||
string-width: 7.2.0
|
||||
strip-ansi: 7.1.2
|
||||
wrap-ansi: 9.0.2
|
||||
|
||||
clone@1.0.4: {}
|
||||
|
||||
clsx@2.1.1: {}
|
||||
@@ -13604,6 +13905,11 @@ snapshots:
|
||||
core-util-is: 1.0.3
|
||||
esprima: 4.0.1
|
||||
|
||||
compare-func@2.0.0:
|
||||
dependencies:
|
||||
array-ify: 1.0.0
|
||||
dot-prop: 5.3.0
|
||||
|
||||
component-emitter@1.3.1: {}
|
||||
|
||||
concat-stream@2.0.0:
|
||||
@@ -13630,6 +13936,19 @@ snapshots:
|
||||
|
||||
content-type@1.0.5: {}
|
||||
|
||||
conventional-changelog-angular@8.3.1:
|
||||
dependencies:
|
||||
compare-func: 2.0.0
|
||||
|
||||
conventional-changelog-conventionalcommits@9.3.1:
|
||||
dependencies:
|
||||
compare-func: 2.0.0
|
||||
|
||||
conventional-commits-parser@6.4.0:
|
||||
dependencies:
|
||||
'@simple-libs/stream-utils': 1.2.0
|
||||
meow: 13.2.0
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie-signature@1.2.2: {}
|
||||
@@ -13654,6 +13973,13 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
cosmiconfig-typescript-loader@6.3.0(@types/node@25.5.0)(cosmiconfig@9.0.1(typescript@5.9.3))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
cosmiconfig: 9.0.1(typescript@5.9.3)
|
||||
jiti: 2.6.1
|
||||
typescript: 5.9.3
|
||||
|
||||
cosmiconfig@8.3.6(typescript@5.9.3):
|
||||
dependencies:
|
||||
import-fresh: 3.3.1
|
||||
@@ -13672,6 +13998,15 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
cosmiconfig@9.0.1(typescript@5.9.3):
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
create-require@1.1.1: {}
|
||||
|
||||
cron-parser@4.9.0:
|
||||
@@ -13883,6 +14218,10 @@ snapshots:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
dot-prop@5.3.0:
|
||||
dependencies:
|
||||
is-obj: 2.0.0
|
||||
|
||||
dot@2.0.0-beta.1: {}
|
||||
|
||||
dotenv-expand@12.0.1:
|
||||
@@ -14078,6 +14417,8 @@ snapshots:
|
||||
is-date-object: 1.1.0
|
||||
is-symbol: 1.1.1
|
||||
|
||||
es-toolkit@1.46.1: {}
|
||||
|
||||
es6-shim@0.35.8: {}
|
||||
|
||||
esbuild@0.27.7:
|
||||
@@ -14715,8 +15056,6 @@ snapshots:
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-east-asian-width@1.4.0: {}
|
||||
|
||||
get-east-asian-width@1.5.0: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
@@ -14759,6 +15098,14 @@ snapshots:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
git-raw-commits@5.0.1(conventional-commits-parser@6.4.0):
|
||||
dependencies:
|
||||
'@conventional-changelog/git-client': 2.7.0(conventional-commits-parser@6.4.0)
|
||||
meow: 13.2.0
|
||||
transitivePeerDependencies:
|
||||
- conventional-commits-filter
|
||||
- conventional-commits-parser
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -14802,6 +15149,10 @@ snapshots:
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
|
||||
global-directory@5.0.0:
|
||||
dependencies:
|
||||
ini: 6.0.0
|
||||
|
||||
globals@14.0.0: {}
|
||||
|
||||
globals@16.4.0: {}
|
||||
@@ -14968,6 +15319,8 @@ snapshots:
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ini@6.0.0: {}
|
||||
|
||||
internal-slot@1.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -15054,7 +15407,7 @@ snapshots:
|
||||
|
||||
is-fullwidth-code-point@5.1.0:
|
||||
dependencies:
|
||||
get-east-asian-width: 1.4.0
|
||||
get-east-asian-width: 1.5.0
|
||||
|
||||
is-generator-fn@2.1.0: {}
|
||||
|
||||
@@ -15085,6 +15438,10 @@ snapshots:
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
is-obj@2.0.0: {}
|
||||
|
||||
is-plain-obj@4.1.0: {}
|
||||
|
||||
is-potential-custom-element-name@1.0.1: {}
|
||||
|
||||
is-promise@4.0.0: {}
|
||||
@@ -15518,8 +15875,7 @@ snapshots:
|
||||
|
||||
jiti@1.21.7: {}
|
||||
|
||||
jiti@2.6.1:
|
||||
optional: true
|
||||
jiti@2.6.1: {}
|
||||
|
||||
joi@18.0.2:
|
||||
dependencies:
|
||||
@@ -15883,6 +16239,8 @@ snapshots:
|
||||
dependencies:
|
||||
fs-monkey: 1.1.0
|
||||
|
||||
meow@13.2.0: {}
|
||||
|
||||
merge-descriptors@2.0.0: {}
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
@@ -17072,7 +17430,7 @@ snapshots:
|
||||
string-width@7.2.0:
|
||||
dependencies:
|
||||
emoji-regex: 10.6.0
|
||||
get-east-asian-width: 1.4.0
|
||||
get-east-asian-width: 1.5.0
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
string-width@8.2.0:
|
||||
@@ -17939,6 +18297,8 @@ snapshots:
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs-parser@22.0.0: {}
|
||||
|
||||
yargs@17.7.2:
|
||||
dependencies:
|
||||
cliui: 8.0.1
|
||||
@@ -17949,6 +18309,15 @@ snapshots:
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yargs@18.0.0:
|
||||
dependencies:
|
||||
cliui: 9.0.1
|
||||
escalade: 3.2.0
|
||||
get-caller-file: 2.0.5
|
||||
string-width: 7.2.0
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 22.0.0
|
||||
|
||||
yn@3.1.1: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Schema Deltas
|
||||
|
||||
Incremental SQL scripts applied to existing environments **after** the canonical schema
|
||||
(`../lcbp3-v1.8.0-schema-02-tables.sql`) has been updated.
|
||||
(`../lcbp3-v1.9.0-schema-02-tables.sql`) has been updated.
|
||||
|
||||
## Naming Convention
|
||||
|
||||
@@ -17,7 +17,7 @@ Examples:
|
||||
|
||||
## Rules (per ADR-009)
|
||||
|
||||
1. **Never replace** the canonical `lcbp3-v1.8.x-schema-02-tables.sql` — update it first, then add the delta here.
|
||||
1. **Never replace** the canonical `lcbp3-v1.9.x-schema-02-tables.sql` — update it first, then add the delta here.
|
||||
2. **Idempotent where possible** — prefer `CREATE TABLE IF NOT EXISTS`, `ALTER TABLE … ADD COLUMN IF NOT EXISTS`, etc.
|
||||
3. **No TypeORM migrations** — these `.sql` files are the only schema deployment mechanism.
|
||||
4. **Data backfill** goes through **n8n workflows**, not this directory.
|
||||
@@ -61,5 +61,5 @@ irreversible — document in the delta header when rollback is impossible.
|
||||
## References
|
||||
|
||||
- [ADR-009 Database Migration Strategy](../../06-Decision-Records/ADR-009-database-migration-strategy.md)
|
||||
- [Canonical Schema](../lcbp3-v1.8.0-schema-02-tables.sql)
|
||||
- [Canonical Schema](../lcbp3-v1.9.0-schema-02-tables.sql)
|
||||
- [Data Dictionary](../03-01-data-dictionary.md)
|
||||
|
||||
@@ -175,81 +175,3 @@ services:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3. ClamAV (Antivirus scanning for file uploads — ADR-016)
|
||||
# Service Name: clamav (Backend อ้างอิง CLAMAV_HOST=clamav, port 3310)
|
||||
# ----------------------------------------------------------------
|
||||
clamav:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: clamav/clamav:1.4.4
|
||||
container_name: clamav
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 1G
|
||||
environment:
|
||||
CLAMAV_NO_LOG_FILE: 'true' # ปิดการเขียนไฟล์ clamd.log
|
||||
FRESHCLAM_NO_LOG_FILE: 'true' # ปิดการเขียนไฟล์ freshclam.log
|
||||
TZ: 'Asia/Bangkok'
|
||||
CLAMAV_NO_FRESHCLAMD: 'false'
|
||||
CLAMAV_NO_CLAMD: 'false'
|
||||
CLAMD_STARTUP_TIMEOUT: '1800'
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
# cache definitions เพื่อไม่ต้อง download ทุกครั้งที่ restart
|
||||
- '/share/np-dms/clamav/data:/var/lib/clamav'
|
||||
- '/share/np-dms/data/logs/clamav:/var/log/clamav'
|
||||
healthcheck:
|
||||
test: ['CMD', 'clamdcheck.sh']
|
||||
interval: 60s
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
start_period: 300s
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 4. Qdrant (Vector Database for RAG — ADR-023A)
|
||||
# Service Name: qdrant (Backend อ้างอิง QDRANT_HOST=qdrant, port 6333)
|
||||
# ----------------------------------------------------------------
|
||||
qdrant:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: qdrant/qdrant:v1.7.0
|
||||
container_name: qdrant
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 512M
|
||||
environment:
|
||||
TZ: 'Asia/Bangkok'
|
||||
QDRANT__SERVICE__GRPC_PORT: '6334'
|
||||
QDRANT__LOG_LEVEL: 'INFO'
|
||||
networks:
|
||||
- lcbp3
|
||||
ports:
|
||||
- '6333:6333' # HTTP API
|
||||
- '6334:6334' # gRPC API
|
||||
volumes:
|
||||
- '/share/np-dms/qdrant/storage:/qdrant/storage'
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:6333/health']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# sudo chown -R 100:101 /share/np-dms/data/logs/clamav
|
||||
# sudo chmod -R 755 /share/np-dms/data/logs/climax
|
||||
|
||||
# sudo chown -R 100:101 /share/np-dms/clamav/data
|
||||
# sudo chmod -R 775 /share/np-dms/clamav/data
|
||||
|
||||
# sudo mkdir -p /share/np-dms/qdrant/storage
|
||||
# sudo chown -R 100:101 /share/np-dms/qdrant/storage
|
||||
|
||||
@@ -124,3 +124,83 @@ services:
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3. ClamAV (Antivirus scanning for file uploads — ADR-016)
|
||||
# Service Name: clamav (Backend อ้างอิง CLAMAV_HOST=clamav, port 3310)
|
||||
# ----------------------------------------------------------------
|
||||
clamav:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: clamav/clamav:1.4.4
|
||||
container_name: clamav
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 1G
|
||||
environment:
|
||||
CLAMAV_NO_LOG_FILE: 'true' # ปิดการเขียนไฟล์ clamd.log
|
||||
FRESHCLAM_NO_LOG_FILE: 'true' # ปิดการเขียนไฟล์ freshclam.log
|
||||
TZ: 'Asia/Bangkok'
|
||||
CLAMAV_NO_FRESHCLAMD: 'false'
|
||||
CLAMAV_NO_CLAMD: 'false'
|
||||
CLAMD_STARTUP_TIMEOUT: '1800'
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
# cache definitions เพื่อไม่ต้อง download ทุกครั้งที่ restart
|
||||
- '/share/np-dms/clamav/data:/var/lib/clamav'
|
||||
- '/share/np-dms/data/logs/clamav:/var/log/clamav'
|
||||
healthcheck:
|
||||
test: ['CMD', 'clamdcheck.sh']
|
||||
interval: 60s
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
start_period: 300s
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 4. Qdrant (Vector Database for RAG — ADR-023A)
|
||||
# Service Name: qdrant (Backend อ้างอิง QDRANT_HOST=qdrant, port 6333)
|
||||
# ----------------------------------------------------------------
|
||||
qdrant:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: qdrant/qdrant:v1.7.0
|
||||
container_name: qdrant
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 512M
|
||||
environment:
|
||||
TZ: 'Asia/Bangkok'
|
||||
QDRANT__SERVICE__GRPC_PORT: '6334'
|
||||
QDRANT__LOG_LEVEL: 'INFO'
|
||||
networks:
|
||||
- lcbp3
|
||||
ports:
|
||||
- '6333:6333' # HTTP API
|
||||
- '6334:6334' # gRPC API
|
||||
volumes:
|
||||
- '/share/np-dms/qdrant/storage:/qdrant/storage'
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:6333/health']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# sudo chown -R 100:101 /share/np-dms/data/logs/clamav
|
||||
# sudo chmod -R 755 /share/np-dms/data/logs/climax
|
||||
|
||||
# sudo chown -R 100:101 /share/np-dms/clamav/data
|
||||
# sudo chmod -R 775 /share/np-dms/clamav/data
|
||||
|
||||
# sudo mkdir -p /share/np-dms/qdrant/storage
|
||||
# sudo chown -R 100:101 /share/np-dms/qdrant/storage
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 04.1 Infrastructure Setup & Docker Compose
|
||||
|
||||
**Project:** LCBP3-DMS
|
||||
**Version:** 1.8.0
|
||||
**Version:** 1.9.0
|
||||
**Status:** Active
|
||||
**Owner:** Nattanin Peancharoen / DevOps Team
|
||||
**Last Updated:** 2026-02-23
|
||||
**Owner:** Nattanin Peancharoen (System Architect / Release Manager / Product Owner)
|
||||
**Last Updated:** 2026-05-16
|
||||
|
||||
> 📍 **Primary Server:** QNAP TS-473A (Application & Database)
|
||||
> 💾 **Backup Server:** ASUSTOR AS5403T (Infrastructure & Backup)
|
||||
@@ -17,11 +17,73 @@ This document serves as the authoritative guide for the environment setup, Docke
|
||||
|
||||
---
|
||||
|
||||
# Infrastructure Distribution
|
||||
|
||||
> 📍 **Multi-Server Architecture:** LCBP3-DMS ใช้ distributed infrastructure แบ่งการทำงานระหว่าง 2 เครื่องหลัก
|
||||
|
||||
## Server Roles
|
||||
|
||||
| Server | IP Address | Role | Services |
|
||||
|--------|------------|------|----------|
|
||||
| **QNAP TS-473A** | 192.168.10.8 | Primary Application & Database Server | Backend, Frontend, MariaDB, Redis (cache), Elasticsearch (search), Qdrant, ClamAV, Monitoring Exporters |
|
||||
| **ASUSTOR AS5403T** | 192.168.10.9 | Infrastructure & Backup Server | Prometheus, Grafana, Gitea Runner |
|
||||
|
||||
## Service Distribution
|
||||
|
||||
### QNAP (192.168.10.8)
|
||||
|
||||
**Application Stack** (`/share/np-dms/app/docker-compose-app.yml`):
|
||||
- `backend` - NestJS API
|
||||
- `frontend` - Next.js Web App
|
||||
- `qdrant` - Vector Database (ADR-023A)
|
||||
- `clamav` - Antivirus Scanner (ADR-016)
|
||||
|
||||
**Infrastructure Services** (`/share/np-dms/services/docker-compose.yml`):
|
||||
- `cache` - Redis (Caching + Distributed Lock + BullMQ queues)
|
||||
- `search` - Elasticsearch (Full-text Search)\n- `qdrant` - Vector Database for RAG (ADR-023A)\n- `clamav` - Antivirus Scanner (ADR-016)
|
||||
|
||||
**Database Stack** (`/share/np-dms/mariadb/docker-compose.yml`):
|
||||
- `mariadb` - MariaDB Database
|
||||
- `pma` - phpMyAdmin (via NPM proxy only)\n\n**MariaDB Configuration:**\n- Config file location: `/share/np-dms/mariadb/my.cnf` (mount to `/etc/mysql/conf.d/my.cnf:ro`)\n- Backup directory: `/share/dms-data/mariadb/backup`
|
||||
|
||||
**Monitoring Exporters** (`/share/np-dms/monitoring/docker-compose.yml`):
|
||||
- `node-exporter` - System metrics
|
||||
- `cadvisor` - Container metrics
|
||||
- `mysqld-exporter` - Database metrics
|
||||
|
||||
**Supporting Services**:
|
||||
- NPM (Nginx Proxy Manager) - Reverse Proxy & SSL
|
||||
- Gitea - Git Repository
|
||||
- n8n - Workflow Automation
|
||||
- RocketChat - Team Communication
|
||||
|
||||
### ASUSTOR (192.168.10.9)
|
||||
|
||||
**Monitoring Stack**:
|
||||
- `prometheus` - Metrics Collection & Alerting
|
||||
- `grafana` - Visualization & Dashboards
|
||||
- Gitea Runner - CI/CD Pipeline Execution
|
||||
|
||||
## Network Architecture
|
||||
|
||||
**Docker Network:** `lcbp3` (external bridge network)
|
||||
|
||||
- ทุก service บน QNAP เชื่อมต่อผ่าน network `lcbp3` เดียวกัน
|
||||
- Services สื่อสารกันด้วย DNS names (เช่น `backend:3000`, `cache:6379`)
|
||||
- ASUSTOR scrape metrics ผ่าน QNAP exporters ผ่าน network DNS
|
||||
|
||||
**Network Security:**
|
||||
- Elasticsearch และ Redis ไม่ publish ports ออก LAN (ใช้ `expose` เฉพาะภายใน network)
|
||||
- MariaDB publish port 3306 แต่ bind เฉพาะ loopback (127.0.0.1:3306) สำหรับ backup/migration
|
||||
- Public access ผ่าน NPM reverse proxy เท่านั้น
|
||||
|
||||
---
|
||||
|
||||
# Environment Setup & Configuration
|
||||
|
||||
**Project:** LCBP3-DMS
|
||||
**Version:** 1.8.0
|
||||
**Last Updated:** 2025-12-02
|
||||
**Version:** 1.9.0
|
||||
**Last Updated:** 2026-05-16
|
||||
|
||||
---
|
||||
|
||||
@@ -37,34 +99,35 @@ This document describes environment variables, configuration files, and secrets
|
||||
|
||||
```bash
|
||||
# File: backend/.env (DO NOT commit to Git)
|
||||
# Location: /share/np-dms/app/.env (ใช้ร่วมกับ docker-compose-app.yml)
|
||||
|
||||
# Application
|
||||
NODE_ENV=production
|
||||
APP_PORT=3000
|
||||
APP_URL=https://lcbp3-dms.example.com
|
||||
APP_URL=https://lcbp3.np-dms.work
|
||||
|
||||
# Database
|
||||
DB_HOST=lcbp3-mariadb
|
||||
DB_HOST=mariadb
|
||||
DB_PORT=3306
|
||||
DB_USER=lcbp3_user
|
||||
DB_PASS=<STRONG_PASSWORD>
|
||||
DB_NAME=lcbp3_dms
|
||||
DB_DATABASE=lcbp3
|
||||
DB_USERNAME=center
|
||||
DB_PASSWORD=<STRONG_PASSWORD>
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=lcbp3-redis
|
||||
REDIS_HOST=cache
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=<STRONG_PASSWORD>
|
||||
|
||||
# JWT Authentication
|
||||
JWT_SECRET=<RANDOM_256_BIT_SECRET>
|
||||
JWT_EXPIRATION=1h
|
||||
JWT_EXPIRATION=8h
|
||||
JWT_REFRESH_SECRET=<RANDOM_256_BIT_SECRET>
|
||||
JWT_REFRESH_EXPIRATION=7d
|
||||
|
||||
# File Storage
|
||||
UPLOAD_DIR=/app/uploads
|
||||
TEMP_UPLOAD_DIR=/app/uploads/temp
|
||||
MAX_FILE_SIZE=104857600 # 100MB
|
||||
# File Storage (Two-Phase Storage - ADR-016)
|
||||
UPLOAD_TEMP_DIR=/app/uploads/temp
|
||||
UPLOAD_PERMANENT_DIR=/app/uploads/permanent
|
||||
MAX_FILE_SIZE=52428800 # 50MB
|
||||
ALLOWED_FILE_TYPES=pdf,doc,docx,xls,xlsx,dwg,jpg,png
|
||||
|
||||
# SMTP Email
|
||||
@@ -77,20 +140,31 @@ SMTP_FROM="LCBP3-DMS System <noreply@example.com>"
|
||||
# LINE Notify (Optional)
|
||||
LINE_NOTIFY_ENABLED=true
|
||||
|
||||
# ClamAV Virus Scanner
|
||||
# ClamAV Virus Scanner (ADR-016)
|
||||
CLAMAV_HOST=clamav
|
||||
CLAMAV_PORT=3310
|
||||
|
||||
# Elasticsearch
|
||||
ELASTICSEARCH_NODE=http://lcbp3-elasticsearch:9200
|
||||
ELASTICSEARCH_INDEX_PREFIX=lcbp3_
|
||||
ELASTICSEARCH_HOST=search
|
||||
ELASTICSEARCH_PORT=9200
|
||||
ELASTICSEARCH_USERNAME=elastic
|
||||
ELASTICSEARCH_PASSWORD=<STRONG_PASSWORD>
|
||||
|
||||
# Qdrant Vector Database (ADR-023A)
|
||||
QDRANT_HOST=qdrant
|
||||
QDRANT_PORT=6333
|
||||
QDRANT_GRPC_PORT=6334
|
||||
|
||||
# Document Numbering (ADR-002)
|
||||
NUMBERING_LOCK_TIMEOUT=5000
|
||||
NUMBERING_RESERVATION_TTL=300
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE_PATH=/app/logs
|
||||
|
||||
# Frontend URL (for email links)
|
||||
FRONTEND_URL=https://lcbp3-dms.example.com
|
||||
FRONTEND_URL=https://lcbp3.np-dms.work
|
||||
|
||||
# Rate Limiting
|
||||
RATE_LIMIT_TTL=60
|
||||
@@ -101,13 +175,14 @@ RATE_LIMIT_MAX=100
|
||||
|
||||
```bash
|
||||
# File: frontend/.env.local (DO NOT commit to Git)
|
||||
# Location: /share/np-dms/app/.env (ใช้ร่วมกับ docker-compose-app.yml)
|
||||
|
||||
# API Backend
|
||||
NEXT_PUBLIC_API_URL=https://lcbp3-dms.example.com/api
|
||||
NEXT_PUBLIC_API_URL=https://backend.np-dms.work/api
|
||||
|
||||
# Application
|
||||
NEXT_PUBLIC_APP_NAME=LCBP3-DMS
|
||||
NEXT_PUBLIC_APP_VERSION=1.8.0
|
||||
NEXT_PUBLIC_APP_VERSION=1.9.0
|
||||
|
||||
# Feature Flags
|
||||
NEXT_PUBLIC_ENABLE_NOTIFICATIONS=true
|
||||
@@ -118,134 +193,88 @@ NEXT_PUBLIC_ENABLE_LINE_NOTIFY=true
|
||||
|
||||
## 🐳 Docker Compose Configuration
|
||||
|
||||
### Production docker-compose.yml
|
||||
> **⚠️ สำคัญ:** LCBP3-DMS ใช้ **Split Files Architecture** ไม่ใช่ monolithic docker-compose.yml เดียว
|
||||
>
|
||||
> ดูรายละเอียดและไฟล์จริงได้ที่: [Appendix A - Live QNAP Production Configs](#appendix-a--live-qnap-production-configs)
|
||||
|
||||
### Split Files Structure
|
||||
|
||||
```
|
||||
/share/np-dms/
|
||||
├── app/docker-compose-app.yml # Application Stack (backend, frontend)
|
||||
├── mariadb/docker-compose.yml # Database Stack (mariadb, pma)
|
||||
├── services/docker-compose.yml # Infrastructure Services (cache/redis, search/elasticsearch, qdrant, clamav)
|
||||
├── monitoring/docker-compose.yml # Monitoring Exporters (node-exporter, cadvisor, mysqld-exporter)
|
||||
├── npm/docker-compose.yml # Nginx Proxy Manager
|
||||
├── git/docker-compose.yml # Gitea
|
||||
└── n8n/docker-compose.yml # n8n Automation
|
||||
```
|
||||
|
||||
### Production Configuration Overview
|
||||
|
||||
**Application Stack** (`app/docker-compose-app.yml`):
|
||||
- `backend` - NestJS API (port 3000 internal)
|
||||
- `frontend` - Next.js Web App (port 3000 internal)
|
||||
- `qdrant` - Vector Database for RAG (ADR-023A)
|
||||
- `clamav` - Antivirus Scanner (ADR-016)
|
||||
|
||||
**Infrastructure Services** (`services/docker-compose.yml`):
|
||||
- `cache` - Redis 7-alpine (Caching + Distributed Lock + BullMQ)
|
||||
- `search` - Elasticsearch 8.11.1 (Full-text Search)\n- `qdrant` - Vector Database for RAG (ADR-023A)\n- `clamav` - Antivirus Scanner (ADR-016)
|
||||
|
||||
**Database Stack** (`mariadb/docker-compose.yml`):
|
||||
- `mariadb` - MariaDB 11.8
|
||||
- `pma` - phpMyAdmin (via NPM proxy only)\n\n**MariaDB Configuration:**\n- Config file location: `/share/np-dms/mariadb/my.cnf` (mount to `/etc/mysql/conf.d/my.cnf:ro`)\n- Backup directory: `/share/dms-data/mariadb/backup`
|
||||
|
||||
**Network:** ทุก stack ใช้ external network `lcbp3` เดียวกัน
|
||||
|
||||
> **ดู configuration ทั้งหมดได้ที่:** [Appendix A](#appendix-a--live-qnap-production-configs)
|
||||
|
||||
### Legacy Monolithic Example (Deprecated)
|
||||
|
||||
⚠️ **ข้อมูลด้านล่างนี้เป็นตัวอย่างเท่านั้น - อย่าใช้ใน production**
|
||||
|
||||
เอกสารเดิมแสดง monolithic docker-compose.yml แต่ production จริงใช้ split files ดังนี้:
|
||||
|
||||
```yaml
|
||||
# File: docker-compose.yml
|
||||
# ❌ DEPRECATED: อย่าใช้ monolithic approach นี้
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# NGINX Reverse Proxy
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: lcbp3-nginx
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
||||
- nginx-logs:/var/log/nginx
|
||||
depends_on:
|
||||
- backend
|
||||
- frontend
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- lcbp3-network
|
||||
# ... (ใช้ NPM แทน)
|
||||
|
||||
# NestJS Backend
|
||||
backend:
|
||||
image: lcbp3-backend:latest
|
||||
container_name: lcbp3-backend
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
volumes:
|
||||
- uploads:/app/uploads
|
||||
- backend-logs:/app/logs
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
- elasticsearch
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- lcbp3-network
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
# ... (ดู app/docker-compose-app.yml จริง)
|
||||
|
||||
# Next.js Frontend
|
||||
frontend:
|
||||
image: lcbp3-frontend:latest
|
||||
container_name: lcbp3-frontend
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
env_file:
|
||||
- ./frontend/.env.local
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- lcbp3-network
|
||||
# ... (ดู app/docker-compose-app.yml จริง)
|
||||
|
||||
# MariaDB Database
|
||||
mariadb:
|
||||
image: mariadb:11.8
|
||||
container_name: lcbp3-mariadb
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
|
||||
MYSQL_DATABASE: ${DB_NAME}
|
||||
MYSQL_USER: ${DB_USER}
|
||||
MYSQL_PASSWORD: ${DB_PASS}
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
- ./mariadb/init:/docker-entrypoint-initdb.d:ro
|
||||
ports:
|
||||
- '3306:3306'
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- lcbp3-network
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
# ... (ดู mariadb/docker-compose.yml จริง)
|
||||
|
||||
# Redis Cache & Queue
|
||||
redis:
|
||||
image: redis:7.2-alpine
|
||||
container_name: lcbp3-redis
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
ports:
|
||||
- '6379:6379'
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- lcbp3-network
|
||||
container_name: cache
|
||||
# ... (ดู services/docker-compose.yml จริง - service name: cache)
|
||||
|
||||
# Elasticsearch
|
||||
elasticsearch:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
|
||||
container_name: lcbp3-elasticsearch
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
- 'ES_JAVA_OPTS=-Xms512m -Xmx512m'
|
||||
- xpack.security.enabled=false
|
||||
volumes:
|
||||
- elasticsearch-data:/usr/share/elasticsearch/data
|
||||
ports:
|
||||
- '9200:9200'
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- lcbp3-network
|
||||
container_name: search
|
||||
# ... (ดู services/docker-compose.yml จริง - service name: search)
|
||||
|
||||
# ClamAV (Optional - for virus scanning)
|
||||
clamav:
|
||||
image: clamav/clamav:latest
|
||||
container_name: lcbp3-clamav
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- lcbp3-network
|
||||
|
||||
networks:
|
||||
lcbp3-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mariadb-data:
|
||||
redis-data:
|
||||
elasticsearch-data:
|
||||
uploads:
|
||||
backend-logs:
|
||||
nginx-logs:
|
||||
# ... (ดู app/docker-compose-app.yml จริง)
|
||||
```
|
||||
|
||||
### Development docker-compose.override.yml
|
||||
@@ -291,49 +320,169 @@ services:
|
||||
|
||||
elasticsearch:
|
||||
environment:
|
||||
- 'ES_JAVA_OPTS=-Xms256m -Xmx256m' # Lower memory for dev
|
||||
- 'ES_JAVA_OPTS=-Xms512m -Xmx512m' # Lower memory for dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Secrets Management
|
||||
## 🔒 Secrets Management\n\n> **🔄 Update:** LCBP3-DMS ใช้ **.env files** แทน Docker secrets เพื่อความเข้ากันได้กับมาตราฐานทั่วไปและ CI/CD pipeline\n\n### Why .env Files?\n\n- **Industry Standard:** .env files เป็นมาตราฐานทั่วไปสำหรับ environment configuration\n- **CI/CD Friendly:** ง่ายต่อการจัดการผ่าน GitHub Actions secrets และ Gitea secrets\n- **QNAP Container Station 3.x:** รองรับ `env_file` อย่างเต็มที่\n- **Simplicity:** ไม่ต้องจัดการกับ Docker secrets ที่ซับซ้อน\n\n### .env Files Structure\n\n```\n/share/np-dms/\n├── app/.env # Application stack secrets (backend, frontend, qdrant, clamav)\n├── mariadb/.env # Database secrets\n├── services/.env # Infrastructure services secrets (cache, search)\n├── npm/.env # NPM secrets\n├── git/.env # Gitea secrets\n└── n8n/.env # n8n secrets\n```\n\n### Creating .env Files\n\n```bash\n# Copy example template\ncp .env.example .env\n\n# Edit with actual values\nnano .env\n\n# Set restrictive permissions\nchmod 600 .env\n```\n\n### Security Best Practices\n\n- **File Permissions:** `chmod 600 .env` (read/write for owner only)\n- **Git Ignore:** เพิ่ม `.env` ใน `.gitignore`\n- **Environment-Specific:** ใช้ `.env.production`, `.env.staging` สำหรับ environment ต่างกัน\n- **CI/CD Secrets:** ใช้ GitHub Actions secrets หรือ Gitea secrets สำหรับ CI/CD pipeline\n- **Rotation:** Rotate secrets ทุก 90 วัน (ตาม ADR-016)\n\n### Alternative: Docker Secrets (Legacy)\n\nเอกสารเดิมแนะนำ Docker secrets แต่ LCBP3-DMS เลือกใช้ .env files แทน:\n\n```yaml\n# ❌ DEPRECATED: Docker secrets approach\nsecrets:\n db_password:\n file: ./secrets/db_password.txt\n\nservices:\n backend:\n secrets:\n - db_password\n```\n\n> **หากต้องการใช้ Docker secrets:** ดู Docker documentation สำหรับการใช้งาน แต่ไม่แนะนำสำหรับ LCBP3-DMS
|
||||
|
||||
### Using Docker Secrets (Recommended for Production)
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
backend:
|
||||
secrets:
|
||||
- db_password
|
||||
- jwt_secret
|
||||
environment:
|
||||
DB_PASS_FILE: /run/secrets/db_password
|
||||
JWT_SECRET_FILE: /run/secrets/jwt_secret
|
||||
|
||||
secrets:
|
||||
db_password:
|
||||
file: ./secrets/db_password.txt
|
||||
jwt_secret:
|
||||
file: ./secrets/jwt_secret.txt
|
||||
```
|
||||
|
||||
### Generate Strong Secrets
|
||||
### Using .env Files (Recommended for Production)
|
||||
|
||||
```bash
|
||||
# Generate JWT Secret
|
||||
openssl rand -base64 64
|
||||
# File: /share/np-dms/app/.env
|
||||
# Location: /share/np-dms/app/.env (ใช้ร่วมกับ docker-compose-app.yml)
|
||||
# ⚠️ DO NOT commit to Git
|
||||
|
||||
# Generate Database Password
|
||||
openssl rand -base64 32
|
||||
# Database
|
||||
DB_PASSWORD=<STRONG_RANDOM_PASSWORD>
|
||||
|
||||
# Generate Redis Password
|
||||
openssl rand -base64 32
|
||||
# Redis
|
||||
REDIS_PASSWORD=<STRONG_RANDOM_PASSWORD>
|
||||
|
||||
# JWT Authentication
|
||||
JWT_SECRET=<RANDOM_256_BIT_SECRET>
|
||||
JWT_REFRESH_SECRET=<RANDOM_256_BIT_SECRET>
|
||||
|
||||
# SMTP Email
|
||||
SMTP_PASS=<APP_PASSWORD>
|
||||
|
||||
# Elasticsearch
|
||||
ELASTICSEARCH_PASSWORD=<STRONG_PASSWORD>
|
||||
|
||||
# Qdrant
|
||||
QDRANT_API_KEY=<OPTIONAL_API_KEY>
|
||||
```
|
||||
|
||||
### Docker Compose Integration
|
||||
|
||||
```yaml
|
||||
# File: docker-compose-app.yml
|
||||
services:
|
||||
backend:
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
TZ: 'Asia/Bangkok'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Directory Structure
|
||||
## � Volume/Storage Architecture (QNAP-Specific)
|
||||
|
||||
> **QNAP NAS Storage:** LCBP3-DMS ใช้ QNAP absolute paths แทน Docker named volumes เพื่อประโยชน์จาก NAS storage management
|
||||
|
||||
### Storage Paths on QNAP
|
||||
|
||||
```
|
||||
/share/np-dms/
|
||||
├── app/
|
||||
│ ├── .env # Environment variables (gitignored)
|
||||
│ └── docker-compose-app.yml # Application stack
|
||||
│
|
||||
├── mariadb/
|
||||
│ ├── data/ # MariaDB database files
|
||||
│ ├── my.cnf # MariaDB configuration (mount to /etc/mysql/conf.d/my.cnf:ro)
|
||||
│ └── init/ # Database initialization scripts
|
||||
│
|
||||
├── services/
|
||||
│ ├── cache/
|
||||
│ │ └── data/ # Redis persistence
|
||||
│ └── search/
|
||||
│ └── data/ # Elasticsearch indices
|
||||
│
|
||||
├── monitoring/
|
||||
│ └── docker-compose.yml # Monitoring exporters
|
||||
│
|
||||
├── npm/
|
||||
│ ├── data/ # NPM configuration
|
||||
│ ├── letsencrypt/ # SSL certificates
|
||||
│ └── custom/ # Custom nginx configs
|
||||
│
|
||||
├── git/
|
||||
│ ├── etc/ # Gitea configuration
|
||||
│ ├── lib/ # Gitea libraries
|
||||
│ ├── gitea_repos/ # Git repositories
|
||||
│ └── gitea_registry/ # Container registry
|
||||
│
|
||||
├── n8n/
|
||||
│ ├── /home/node/.n8n # n8n workflow data
|
||||
│ ├── cache/ # n8n cache
|
||||
│ └── scripts/ # n8n custom scripts
|
||||
│
|
||||
└── data/
|
||||
├── uploads/
|
||||
│ ├── temp/ # Temporary upload storage (Two-Phase Storage - ADR-016)
|
||||
│ └── permanent/ # Permanent document storage
|
||||
└── logs/
|
||||
├── backend/ # Backend application logs
|
||||
└── frontend/ # Frontend application logs
|
||||
```
|
||||
|
||||
### Volume Mounts in Docker Compose
|
||||
|
||||
**Application Stack** (`app/docker-compose-app.yml`):
|
||||
```yaml
|
||||
volumes:
|
||||
- '/share/np-dms/data/uploads/temp:/app/uploads/temp'
|
||||
- '/share/np-dms/data/uploads/permanent:/app/uploads/permanent'
|
||||
- '/share/np-dms/data/logs/backend:/app/logs'
|
||||
```
|
||||
|
||||
**Database Stack** (`mariadb/docker-compose.yml`):
|
||||
```yaml
|
||||
volumes:
|
||||
- '/share/np-dms/mariadb/data:/var/lib/mysql'
|
||||
- '/share/np-dms/mariadb/my.cnf:/etc/mysql/conf.d/my.cnf:ro'
|
||||
- '/share/np-dms/mariadb/init:/docker-entrypoint-initdb.d:ro'
|
||||
- '/share/dms-data/mariadb/backup:/backup'
|
||||
```
|
||||
|
||||
**Infrastructure Services** (`services/docker-compose.yml`):
|
||||
```yaml
|
||||
volumes:
|
||||
- '/share/np-dms/services/cache/data:/data' # Redis
|
||||
- '/share/np-dms/services/search/data:/usr/share/elasticsearch/data' # Elasticsearch
|
||||
```
|
||||
|
||||
### Storage Permissions
|
||||
|
||||
QNAP requires specific UID/GID for container access:
|
||||
|
||||
- **MariaDB:** UID 999
|
||||
- **Redis:** UID 999
|
||||
- **Elasticsearch:** UID 1000
|
||||
- **phpMyAdmin:** UID 33
|
||||
|
||||
Example setup:
|
||||
```bash
|
||||
# MariaDB
|
||||
chown -R 999:999 /share/np-dms/mariadb
|
||||
chmod -R 755 /share/np-dms/mariadb
|
||||
|
||||
# Redis
|
||||
chown -R 999:999 /share/np-dms/services/cache/data
|
||||
chmod -R 750 /share/np-dms/services/cache/data
|
||||
|
||||
# Elasticsearch
|
||||
chown -R 1000:1000 /share/np-dms/services/search/data
|
||||
chmod -R 750 /share/np-dms/services/search/data
|
||||
```
|
||||
|
||||
### Backup Strategy
|
||||
|
||||
QNAP provides native snapshot backup for `/share/np-dms/` directory:
|
||||
- **Frequency:** Daily snapshots (configurable in QNAP Hybrid Backup Sync)
|
||||
- **Retention:** 7-30 days (configurable)
|
||||
- **Target:** External USB drive or remote NAS
|
||||
|
||||
Additional application-level backups:
|
||||
- **Database:** mysqldump scripts (see Backup & Recovery section)
|
||||
- **Configuration:** `/share/np-dms/*.env` files (manual backup)
|
||||
|
||||
---
|
||||
|
||||
## �� Directory Structure
|
||||
|
||||
```
|
||||
lcbp3/
|
||||
@@ -451,7 +600,7 @@ docker exec lcbp3-backend env | grep DB_
|
||||
|
||||
```bash
|
||||
# Test Redis connection
|
||||
docker exec lcbp3-redis redis-cli -a <PASSWORD> ping
|
||||
docker exec cache redis-cli -a <PASSWORD> ping
|
||||
# Should return: PONG
|
||||
```
|
||||
|
||||
@@ -475,15 +624,15 @@ docker exec lcbp3-backend env | grep NODE_ENV
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.8.0
|
||||
**Last Review:** 2025-12-01
|
||||
**Next Review:** 2026-03-01
|
||||
**Version:** 1.9.0
|
||||
**Last Review:** 2026-05-16
|
||||
**Next Review:** 2026-08-16
|
||||
|
||||
---
|
||||
|
||||
# Infrastructure Setup
|
||||
|
||||
> 📍 **Document Version:** v1.8.0
|
||||
> 📍 **Document Version:** v1.9.0
|
||||
> 🖥️ **Primary Server:** QNAP TS-473A (Application & Database)
|
||||
> 💾 **Backup Server:** ASUSTOR AS5403T (Infrastructure & Backup)
|
||||
|
||||
@@ -508,7 +657,7 @@ version: '3.8'
|
||||
services:
|
||||
redis:
|
||||
image: 'redis:7.2-alpine'
|
||||
container_name: lcbp3-redis
|
||||
container_name: cache
|
||||
restart: unless-stopped
|
||||
# AOF: Enabled for durability
|
||||
# Maxmemory: Prevent OOM
|
||||
@@ -840,7 +989,7 @@ curl -X POST http://admin:admin@localhost:3000/api/dashboards/db \
|
||||
|
||||
### 5.1 Database Backup Strategy
|
||||
|
||||
#### Automated Backup Script
|
||||
> **⚠️ Note:** Scripts ด้านล่างนี้เป็นตัวอย่าง/เทมเพลตเท่านั้น - วิธีการจริงจะตัดสินใจภายหลังตามความเหมาะสมกับ infrastructure\n\n#### Automated Backup Script (Example)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@@ -998,7 +1147,7 @@ docker exec cache redis-cli ping
|
||||
|
||||
---
|
||||
|
||||
## 6. Maintenance Procedures
|
||||
## 6. Maintenance Procedures\n\n> **⚠️ Note:** Procedures ด้านล่างนี้เป็น template/guidelines เท่านั้น - วิธีการจริงจะตัดสินใจภายหลังตามความเหมาะสมกับ infrastructure
|
||||
|
||||
### 6.1 Sequence Adjustment
|
||||
|
||||
@@ -1107,16 +1256,16 @@ LINES TERMINATED BY '\n';
|
||||
echo "🧹 Cleaning up expired reservations..."
|
||||
|
||||
# Get all reservation keys
|
||||
KEYS=$(docker exec lcbp3-redis-1 redis-cli --cluster call 172.20.0.2:6379 KEYS "reservation:*" | grep -v "(error)")
|
||||
KEYS=$(docker exec cache-1 redis-cli --cluster call 172.20.0.2:6379 KEYS "reservation:*" | grep -v "(error)")
|
||||
|
||||
COUNT=0
|
||||
for KEY in $KEYS; do
|
||||
# Check TTL
|
||||
TTL=$(docker exec lcbp3-redis-1 redis-cli TTL "$KEY")
|
||||
TTL=$(docker exec cache-1 redis-cli TTL "$KEY")
|
||||
|
||||
if [ "$TTL" -lt 0 ]; then
|
||||
# Delete expired key
|
||||
docker exec lcbp3-redis-1 redis-cli DEL "$KEY"
|
||||
docker exec cache-1 redis-cli DEL "$KEY"
|
||||
((COUNT++))
|
||||
fi
|
||||
done
|
||||
@@ -1236,8 +1385,8 @@ echo "⚠️ Please verify system functionality manually"
|
||||
1. Check Redis cluster health
|
||||
|
||||
```bash
|
||||
docker exec lcbp3-redis-1 redis-cli cluster info
|
||||
docker exec lcbp3-redis-1 redis-cli cluster nodes
|
||||
docker exec cache-1 redis-cli cluster info
|
||||
docker exec cache-1 redis-cli cluster nodes
|
||||
```
|
||||
|
||||
2. Check database locks
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 🚀 Release Management Policy — LCBP3-DMS v1.8.0
|
||||
# 🚀 Release Management Policy — LCBP3-DMS v1.9.0
|
||||
|
||||
---
|
||||
|
||||
title: 'Release Management Policy, Versioning Strategy, and Deployment Gates'
|
||||
version: 1.0.0
|
||||
version: 1.1.0
|
||||
status: DRAFT
|
||||
owner: Nattanin Peancharoen (System Architect / Release Manager)
|
||||
last_updated: 2026-03-11
|
||||
owner: Nattanin Peancharoen (System Architect / Release Manager / Product Owner)
|
||||
last_updated: 2026-05-16
|
||||
related:
|
||||
|
||||
- specs/04-Infrastructure-OPS/04-04-deployment-guide.md ← Blue-Green Deployment Detail
|
||||
@@ -17,7 +17,7 @@ related:
|
||||
---
|
||||
|
||||
> [!IMPORTANT]
|
||||
> ทุก Release สู่ Production **ต้องผ่าน Release Gate** — ไม่มีข้อยกเว้น
|
||||
> ทุก Release สู่ Production **ต้องผ่าน Release Gate** — มีข้อยกเว้นเฉพาะ P0 Emergency เท่านั้น
|
||||
> เอกสารนี้กำหนด Policy ที่ทุกคนในทีมต้องปฏิบัติตาม
|
||||
|
||||
---
|
||||
@@ -74,12 +74,14 @@ lcbp3-backend:v1.8.0-rc.1 ← Release Candidate
|
||||
|
||||
| Release Type | Cadence | Who Approves | Notes |
|
||||
| -------------------------- | ------------- | ---------------------------- | --------------------- |
|
||||
| **Sprint Release** (Minor) | ทุก 2 สัปดาห์ | PO + Lead Dev | ตามแผน Sprint |
|
||||
| **Hotfix** (Patch) | ตามเหตุการณ์ | Lead Dev (P0/P1) → PO Notify | ไม่รอ Sprint |
|
||||
| **Emergency Hotfix** | ทันที (P0) | Lead Dev → แจ้ง PO พร้อมกัน | Security, System Down |
|
||||
| **Sprint Release** (Minor) | ทุก 2 สัปดาห์ (Ideal State) | PO + Lead Dev | ตามแผน Sprint |
|
||||
| **Hotfix** (Patch) | ตามเหตุการณ์ | System Architect/DevOps → PO Notify | ไม่รอ Sprint |
|
||||
| **Emergency Hotfix** | ทันที (P0) | System Architect/DevOps → แจ้ง PO พร้อมกัน | Security, System Down |
|
||||
| **Major Release** | กำหนดโดย PO | PO + กทท. Sign-off | Phase Change |
|
||||
|
||||
### Sprint Release Calendar (ตัวอย่าง)
|
||||
### Sprint Release Calendar (Ideal State - Not Yet Implemented)
|
||||
|
||||
> **หมายเหตุ:** Sprint cadence ทุก 2 สัปดาห์เป็น ideal state ยังไม่ได้ทำจริงในปัจจุบัน ปัจจุบะทำ release แบบ as-needed
|
||||
|
||||
```
|
||||
Sprint 1: 01–14 มี.ค. 2569 → Release v1.9.0 (28 มี.ค.)
|
||||
@@ -105,14 +107,16 @@ Sprint 2: 15–28 มี.ค. 2569 → Release v1.10.0 (11 เม.ย.)
|
||||
| -------------------------- | ------------------ | ----------------------------------- |
|
||||
| **TypeScript Compile** | `tsc --noEmit` | 0 Errors |
|
||||
| **Unit Tests Pass** | Jest | ≥ 80% Pass Rate |
|
||||
| **E2E Tests (Core Flows)** | Playwright/Cypress | 100% Core Flows ผ่าน |
|
||||
| **E2E Tests (Core Flows)** | Playwright | 100% Core Flows ผ่าน |
|
||||
| **Security Scan** | `npm audit` | 0 Critical/High Vulnerabilities |
|
||||
| **UUID Misuse Check** | grep script | 0 parseInt on UUID (ADR-019) |
|
||||
| **Console.log Check** | grep script | 0 console.log in committed code |
|
||||
| **Lint** | ESLint | 0 Errors (Warnings ยอมรับได้) |
|
||||
| **Build Success** | Docker Build | Exit 0 |
|
||||
| **Image Size** | Docker inspect | < 2GB (Backend), < 1.5GB (Frontend) |
|
||||
|
||||
**Owner:** Lead Dev
|
||||
**Tool:** Gitea CI/CD Pipeline (ADR-015)
|
||||
**Owner:** System Architect/DevOps
|
||||
**Tool:** Gitea CI/CD Pipeline (.gitea/workflows/ci-deploy.yml)
|
||||
|
||||
---
|
||||
|
||||
@@ -123,10 +127,10 @@ Sprint 2: 15–28 มี.ค. 2569 → Release v1.10.0 (11 เม.ย.)
|
||||
| Deploy to Staging Environment | สำเร็จ, ไม่มี Error | DevOps |
|
||||
| Health Check `/health` → 200 | ✅ | Automated |
|
||||
| Smoke Test (Manual): Login → Create Correspondence → Submit | ผ่าน | Dev หรือ QA |
|
||||
| Migration Script (ถ้ามี Schema Change) | รันสำเร็จบน Staging Schema | DBA / Dev |
|
||||
| Migration Script (ถ้ามี Schema Change) | รันสำเร็จบน Staging Schema | System Architect/DevOps |
|
||||
| Rollback Test: Deploy → Rollback → Verify | ระบบ Rollback ได้ใน < 5 นาที | DevOps |
|
||||
|
||||
**Owner:** Nattanin P.
|
||||
**Owner:** Nattanin P. (System Architect / Release Manager / Product Owner)
|
||||
|
||||
---
|
||||
|
||||
@@ -138,11 +142,11 @@ PO Review: ✅ ไม่มี Known Blocker Issues?
|
||||
PO Sign-off: ✅ อนุมัติ Release
|
||||
|
||||
ถ้ามี Schema Change:
|
||||
DBA Confirm: ✅ Schema SQL พร้อม Apply บน Production
|
||||
DBA Confirm: ✅ Rollback SQL พร้อม (ถ้าจำเป็น)
|
||||
System Architect/DevOps Confirm: ✅ Schema SQL พร้อม Apply บน Production
|
||||
System Architect/DevOps Confirm: ✅ Rollback SQL พร้อม (ถ้าจำเป็น)
|
||||
```
|
||||
|
||||
**Owner:** Nattanin P. (PO + Release Manager)
|
||||
**Owner:** Nattanin P. (System Architect / Release Manager / Product Owner)
|
||||
|
||||
---
|
||||
|
||||
@@ -155,8 +159,8 @@ PO Sign-off: ✅ อนุมัติ Release
|
||||
2. Post-Deploy Verification (15 นาที):
|
||||
✅ Health Check: All containers healthy
|
||||
✅ Smoke Test: Login + Core Feature
|
||||
✅ Error Rate: < 1% (Grafana) ใน 15 นาทีแรก
|
||||
✅ Response Time: P90 < 500ms (Grafana)
|
||||
✅ Error Rate: < 1% (Grafana on ASUSTOR) ใน 15 นาทีแรก
|
||||
✅ Response Time: P90 < 500ms (Grafana on ASUSTOR)
|
||||
|
||||
3. ถ้าผ่าน → RELEASE COMPLETE ✅
|
||||
4. ถ้าไม่ผ่าน → ROLLBACK ทันที (rollback.sh)
|
||||
@@ -367,50 +371,39 @@ Security Check: npm audit (ถ้าเป็น Security Bug)
|
||||
### Pipeline Stages (ทุก PR เข้า `develop` หรือ `release/*`)
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/ci.yml (ตัวอย่าง Structure)
|
||||
# .gitea/workflows/ci-deploy.yml (Actual Implementation)
|
||||
|
||||
stages:
|
||||
- name: "1. Code Quality"
|
||||
jobs:
|
||||
- typecheck # tsc --noEmit
|
||||
- lint # ESLint
|
||||
- unit-test # Jest (coverage report)
|
||||
jobs:
|
||||
# JOB 1: CI & Quality Gate
|
||||
build:
|
||||
steps:
|
||||
- pnpm install --frozen-lockfile
|
||||
- pnpm lint
|
||||
- Security checks:
|
||||
- UUID misuse check (grep parseInt.*uuid)
|
||||
- Console.log check (grep console.log)
|
||||
- pnpm test (backend + frontend)
|
||||
|
||||
- name: "2. Security"
|
||||
jobs:
|
||||
- dependency-audit # npm audit
|
||||
- secret-scan # gitleaks (ตรวจ Secret ใน Code)
|
||||
|
||||
- name: "3. Build"
|
||||
jobs:
|
||||
- build-backend # docker build lcbp3-backend:${BRANCH_SHA}
|
||||
- build-frontend # docker build lcbp3-frontend:${BRANCH_SHA}
|
||||
|
||||
- name: "4. Integration Test" (เฉพาะ release/* branch)
|
||||
jobs:
|
||||
- deploy-staging # Deploy to Staging Environment
|
||||
- smoke-test # Playwright Smoke Test
|
||||
- api-test # Postman/Newman Core API Tests
|
||||
|
||||
- name: "5. Release" (เฉพาะ main branch, Manual Trigger)
|
||||
jobs:
|
||||
- tag-version # git tag vX.Y.Z
|
||||
- push-registry # Push image ไปยัง Internal Registry
|
||||
- deploy-prod # deploy.sh (Blue-Green)
|
||||
- notify # LINE Notification
|
||||
# JOB 2: Deploy — Trigger Blue-Green on QNAP (main branch only)
|
||||
deploy:
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- SSH to QNAP
|
||||
- git pull origin main
|
||||
- ./scripts/deploy.sh
|
||||
```
|
||||
|
||||
> **หมายเหตุ:** Pipeline จริงมี 2 jobs เท่านั้น ไม่มี stages แยก 5 ขั้นตอนตามที่เคยระบุในเอกสารเดิม
|
||||
|
||||
### Environment Variables ที่ CI/CD ใช้
|
||||
|
||||
```bash
|
||||
# Gitea Secrets (ตั้งค่าใน Gitea Settings → Secrets)
|
||||
REGISTRY_URL=registry.internal.example.com
|
||||
REGISTRY_USERNAME=ci-bot
|
||||
REGISTRY_PASSWORD=<secret>
|
||||
QNAP_SSH_KEY=<private key>
|
||||
QNAP_HOST=192.168.1.x
|
||||
LINE_NOTIFY_TOKEN=<secret>
|
||||
STAGING_URL=https://staging.lcbp3-dms.internal
|
||||
SSH_KEY=<private key for QNAP>
|
||||
HOST=192.168.10.8
|
||||
PORT=22
|
||||
USERNAME=admin
|
||||
```
|
||||
|
||||
---
|
||||
@@ -421,12 +414,14 @@ STAGING_URL=https://staging.lcbp3-dms.internal
|
||||
|
||||
```
|
||||
✅ npm audit: 0 Critical, 0 High vulnerabilities
|
||||
✅ ไม่มี Secret/Credential hardcoded ใน Code (Gitleaks)
|
||||
✅ ไม่มี Secret/Credential hardcoded ใน Code (git secrets check)
|
||||
✅ .env ไม่ถูก Commit (gitignore check)
|
||||
✅ JWT_SECRET และ DB_PASSWORD ไม่ใช่ Default Values
|
||||
✅ Docker Image ไม่มี Root User (USER node)
|
||||
✅ Helmet.js Security Headers ยังทำงาน (Smoke Test)
|
||||
✅ Rate Limiting ยังทำงาน (Login endpoint test)
|
||||
✅ UUID misuse check: 0 parseInt on UUID (ADR-019)
|
||||
✅ Console.log check: 0 console.log in committed code
|
||||
```
|
||||
|
||||
### ข้อห้ามเด็ดขาด (Forbidden in Release)
|
||||
@@ -467,6 +462,8 @@ STAGING_URL=https://staging.lcbp3-dms.internal
|
||||
- [ ] TypeScript 0 Errors
|
||||
- [ ] ESLint 0 Errors
|
||||
- [ ] `npm audit` 0 Critical/High
|
||||
- [ ] UUID misuse check: 0 parseInt on UUID
|
||||
- [ ] Console.log check: 0 console.log in committed code
|
||||
- [ ] Docker Build Success
|
||||
- [ ] CHANGELOG.md Updated
|
||||
- [ ] Delta SQL file ready (ถ้ามี Schema Change)
|
||||
@@ -496,7 +493,15 @@ STAGING_URL=https://staging.lcbp3-dms.internal
|
||||
|
||||
## 📝 Document Control
|
||||
|
||||
- **Version:** 1.0.0 | **Status:** DRAFT
|
||||
- **Created:** 2026-03-11 | **Owner:** Nattanin Peancharoen
|
||||
- **Version:** 1.1.0 | **Status:** DRAFT
|
||||
- **Created:** 2026-03-11 | **Owner:** Nattanin Peancharoen (System Architect / Release Manager / Product Owner)
|
||||
- **Last Updated:** 2026-05-16 | Updated CI/CD pipeline to match actual implementation
|
||||
- **Next Review:** Pre Sprint 1 (T-2 สัปดาห์ก่อน Go-Live)
|
||||
- **Classification:** Internal — Developer + DevOps + PO Only
|
||||
|
||||
## 📝 Change History
|
||||
|
||||
| Version | Date | Changes | Author |
|
||||
|---------|------|---------|---------|
|
||||
| 1.0.0 | 2026-03-11 | Initial release policy document | Nattanin P. |
|
||||
| 1.1.0 | 2026-05-16 | Updated to v1.9.0: Fixed CI/CD pipeline description, clarified monitoring infrastructure (ASUSTOR vs QNAP), updated role references to reflect actual team structure, marked Sprint cadence as ideal state | Nattanin P. |
|
||||
|
||||
Reference in New Issue
Block a user