690612:1407 ADR-035-235 #01
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// File: specs/200-fullstacks/235-ai-runtime-policy-refactor/quickstart.md
|
||||
// Change Log:
|
||||
// - 2026-06-11: Verification quickstart for AI Runtime Policy Refactor
|
||||
// - 2026-06-12: เพิ่ม PowerShell syntax และ environment variable setup
|
||||
|
||||
# Quickstart: AI Runtime Policy Refactor — Verification Guide
|
||||
|
||||
@@ -11,51 +12,188 @@
|
||||
- Ollama running with `np-dms-ai` and `np-dms-ocr` tags registered
|
||||
- Admin user token available
|
||||
|
||||
## Environment Setup
|
||||
|
||||
### การเข้าถึง Backend (สำคัญ)
|
||||
|
||||
จาก `@/specs/04-Infrastructure-OPS/04-00-docker-compose/QNAP/app/docker-compose-app.yml`:
|
||||
|
||||
| Environment | Backend URL | ใช้เมื่อ |
|
||||
|-------------|-------------|----------|
|
||||
| **Production (QNAP + NPM)** | `https://backend.np-dms.work/api` | ทดสอบจากเครื่องภายนอก (WSL, บ้าน) |
|
||||
| **QNAP Internal** | `http://backend:3000` | ทดสอบจากภายใน QNAP (docker network) |
|
||||
| **Local dev** | `http://localhost:3001` | รัน backend บนเครื่องตัวเอง |
|
||||
|
||||
**หมายเหตุ:** Backend container ใช้ port **3000** (ไม่ใช่ 3001) และอยู่ behind nginx proxy manager
|
||||
|
||||
### Bash (Linux/macOS/Git Bash on Windows)
|
||||
|
||||
```bash
|
||||
# สำหรับ Production QNAP (ผ่าน HTTPS + NPM)
|
||||
export BACKEND_URL="https://backend.np-dms.work/api"
|
||||
|
||||
# หรือถ้า SSH tunnel ไป QNAP แล้ว
|
||||
# export BACKEND_URL="http://localhost:3000"
|
||||
|
||||
export TOKEN="your-jwt-token-here"
|
||||
```
|
||||
|
||||
### PowerShell (Windows)
|
||||
|
||||
```powershell
|
||||
# สำหรับ Production QNAP (ผ่าน HTTPS + NPM)
|
||||
$env:BACKEND_URL = "https://backend.np-dms.work/api"
|
||||
|
||||
# หรือถ้า SSH tunnel ไป QNAP แล้ว
|
||||
# $env:BACKEND_URL = "http://localhost:3000"
|
||||
|
||||
$env:TOKEN = "your-jwt-token-here"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### วิธีหา TOKEN
|
||||
|
||||
**วิธีที่ 1: Login ผ่าน API (Bash)**
|
||||
|
||||
```bash
|
||||
# Login แล้วดึง token จาก response
|
||||
# หมายเหตุ: Backend ใช้ 'username' (ไม่ใช่ email) ใน login field
|
||||
RESPONSE=$(curl -s -X POST "$BACKEND_URL/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "admin", "password": "Center2025"}')
|
||||
|
||||
# วิธีดึง TOKEN (เลือก 1 จาก 3):
|
||||
|
||||
# วิธี 1: ใช้ jq (ถ้าติดตั้งแล้ว)
|
||||
# export TOKEN=$(echo $RESPONSE | jq -r '.access_token')
|
||||
|
||||
# วิธี 2: ใช้ Python (ทั่วไปมีอยู่แล้ว) — แนะนำ
|
||||
export TOKEN=$(echo $RESPONSE | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['access_token'])")
|
||||
|
||||
# วิธี 3: ดู response แล้ว copy เอง (ถ้าไม่มีทั้ง jq และ Python)
|
||||
# echo $RESPONSE
|
||||
# export TOKEN="paste_token_here"
|
||||
```
|
||||
|
||||
**วิธีที่ 2: ดึงจาก Browser DevTools**
|
||||
|
||||
1. เปิด browser ไปที่ `http://192.168.10.8:3000` (frontend)
|
||||
2. Login ด้วย account ที่มีสิทธิ์ admin
|
||||
3. กด F12 → Network tab
|
||||
4. รีเฟรชหน้า หรือ ทำ action ใดก็ได้
|
||||
5. ดู request ที่ส่งไป backend → Headers → `Authorization: Bearer eyJhbG...`
|
||||
6. Copy ค่าหลัง `Bearer ` มาใส่ใน `$TOKEN`
|
||||
|
||||
**วิธีที่ 3: ถ้ามี Access ตรงกับ Database**
|
||||
|
||||
```sql
|
||||
-- ดู username ที่มี role = 'admin' (หลังจากนั้นต้อง login ผ่าน API เพื่อเอา token)
|
||||
SELECT username FROM users WHERE role = 'admin' LIMIT 1;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Default Users (จาก Seed Data)
|
||||
|
||||
ถ้าใช้ seed data เริ่มต้น มี users นี้ให้ใช้:
|
||||
|
||||
| Username | Role | Password |
|
||||
|----------|------|----------|
|
||||
| `superadmin` | Superadmin | `Center2025` |
|
||||
| `admin` | Org Admin | `Center2025` |
|
||||
| `editor01` | Editor | `Center2025` |
|
||||
| `viewer01` | Viewer | `Center2025` |
|
||||
|
||||
---
|
||||
|
||||
## Gate 1: Policy Contract Verification
|
||||
|
||||
### 1A. Reject model.key (should return 400)
|
||||
|
||||
**Bash:**
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/api/ai/jobs \
|
||||
curl -X POST "$BACKEND_URL/ai/jobs" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "rag-query", "model": {"key": "typhoon2.5-np-dms:latest"}}' \
|
||||
| jq '.statusCode, .message'
|
||||
| python3 -c "import sys, json; d=json.load(sys.stdin); e=d.get('error', {}); print(e.get('statusCode'), e.get('message'))"
|
||||
# Expected: 400, message about model.key not allowed
|
||||
```
|
||||
|
||||
**PowerShell:**
|
||||
```powershell
|
||||
$body = '{"type": "rag-query", "model": {"key": "typhoon2.5-np-dms:latest"}}'
|
||||
Invoke-RestMethod -Uri "$env:BACKEND_URL/ai/jobs" -Method POST -Headers @{
|
||||
"Authorization" = "Bearer $env:TOKEN"
|
||||
"Content-Type" = "application/json"
|
||||
} -Body $body | Select-Object statusCode, message
|
||||
# Expected: 400, message about model.key not allowed
|
||||
```
|
||||
|
||||
### 1B. Reject parameter overrides (should return 400)
|
||||
|
||||
**Bash:**
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/api/ai/jobs \
|
||||
curl -X POST "$BACKEND_URL/ai/jobs" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "rag-query", "temperature": 0.9}' \
|
||||
| jq '.statusCode'
|
||||
| python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('error', {}).get('statusCode'))"
|
||||
# Expected: 400
|
||||
```
|
||||
|
||||
**PowerShell:**
|
||||
```powershell
|
||||
$body = '{"type": "rag-query", "temperature": 0.9}'
|
||||
(Invoke-RestMethod -Uri "$env:BACKEND_URL/ai/jobs" -Method POST -Headers @{
|
||||
"Authorization" = "Bearer $env:TOKEN"
|
||||
"Content-Type" = "application/json"
|
||||
} -Body $body).statusCode
|
||||
# Expected: 400
|
||||
```
|
||||
|
||||
### 1C. Valid executionProfile (should return 201)
|
||||
|
||||
**Bash:**
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/api/ai/jobs \
|
||||
curl -X POST "$BACKEND_URL/ai/jobs" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "rag-query", "executionProfile": "balanced", "documentPublicId": "<uuid>"}' \
|
||||
| jq '.data.modelUsed'
|
||||
-d '{"type": "rag-query", "executionProfile": "balanced", "documentPublicId": "<uuid-here>"}' \
|
||||
| python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('data', {}).get('modelUsed'))"
|
||||
# Expected: "np-dms-ai"
|
||||
```
|
||||
|
||||
**PowerShell:**
|
||||
```powershell
|
||||
$body = '{"type": "rag-query", "executionProfile": "balanced", "documentPublicId": "<uuid-here>"}'
|
||||
(Invoke-RestMethod -Uri "$env:BACKEND_URL/ai/jobs" -Method POST -Headers @{
|
||||
"Authorization" = "Bearer $env:TOKEN"
|
||||
"Content-Type" = "application/json"
|
||||
} -Body $body).data.modelUsed
|
||||
# Expected: "np-dms-ai"
|
||||
```
|
||||
|
||||
### 1D. large-context by non-admin (should return 403)
|
||||
|
||||
**Bash:**
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/api/ai/jobs \
|
||||
curl -X POST "$BACKEND_URL/ai/jobs" \
|
||||
-H "Authorization: Bearer $NON_ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "rag-query", "executionProfile": "large-context"}' \
|
||||
| jq '.statusCode'
|
||||
| python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('error', {}).get('statusCode'))"
|
||||
# Expected: 403
|
||||
```
|
||||
|
||||
**PowerShell:**
|
||||
```powershell
|
||||
$body = '{"type": "rag-query", "executionProfile": "large-context"}'
|
||||
(Invoke-RestMethod -Uri "$env:BACKEND_URL/ai/jobs" -Method POST -Headers @{
|
||||
"Authorization" = "Bearer $env:NON_ADMIN_TOKEN"
|
||||
"Content-Type" = "application/json"
|
||||
} -Body $body).statusCode
|
||||
# Expected: 403
|
||||
```
|
||||
|
||||
@@ -103,18 +241,34 @@ docker logs ocr-sidecar --tail 20
|
||||
|
||||
### 4A. Force GPU pressure then run RAG
|
||||
|
||||
**Step 1: Force load large model (Bash)**
|
||||
```bash
|
||||
# 1. Force load large model
|
||||
curl http://localhost:11434/api/generate -d '{"model":"np-dms-ai","prompt":"warmup","keep_alive":-1}'
|
||||
# ถ้า Ollama รันบน Desk-5439 (192.168.10.100)
|
||||
curl http://192.168.10.100:11434/api/generate -d '{"model":"np-dms-ai","prompt":"warmup","keep_alive":-1}'
|
||||
```
|
||||
|
||||
# 2. Run RAG query
|
||||
curl -X POST http://localhost:3001/api/ai/jobs \
|
||||
**Step 2: Run RAG query**
|
||||
|
||||
*Bash:*
|
||||
```bash
|
||||
curl -X POST "$BACKEND_URL/ai/jobs" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"type":"rag-query","executionProfile":"balanced","documentPublicId":"<uuid>"}' \
|
||||
| jq '.data.status'
|
||||
# Expected: "completed" (ไม่ fail)
|
||||
```
|
||||
|
||||
# 3. ตรวจ sidecar log
|
||||
*PowerShell:*
|
||||
```powershell
|
||||
$body = '{"type":"rag-query","executionProfile":"balanced","documentPublicId":"<uuid-here>"}'
|
||||
(Invoke-RestMethod -Uri "$env:BACKEND_URL/ai/jobs" -Method POST -Headers @{
|
||||
"Authorization" = "Bearer $env:TOKEN"
|
||||
} -Body $body).data.status
|
||||
# Expected: "completed" (ไม่ fail)
|
||||
```
|
||||
|
||||
**Step 3: ตรวจ sidecar log**
|
||||
```bash
|
||||
docker logs ocr-sidecar --tail 20
|
||||
# Expected: device=cpu reason=gpu-headroom-below-threshold
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user