CORETH — Current System Architecture & Installation
เอกสารฉบับเดียวที่รวมสถาปัตยกรรมปัจจุบัน ขั้นตอนติดตั้ง การตั้งค่า การตรวจสอบความพร้อม และงาน operations ของ CORETH Context Processor ทุกหัวข้อถอดย้อนกลับมาจาก source code, migrations, Docker Compose และ management commands ที่มีอยู่จริงใน repository ไม่ได้คัดลอกจากเอกสารสถาปัตยกรรมรุ่นก่อน
8d2a8657d595d084597f3f0cfea14c77bbcfe2ec (branch main).
เมื่อเอกสารเก่ากับโค้ดขัดกัน ให้ยึดโค้ด/migration/compose/พฤติกรรม runtime เป็นหลักเสมอ
และปรับเอกสารฉบับนี้ตามเมื่อสถาปัตยกรรมเปลี่ยนอีก
1. Overview#
CORETH Context Processor คือระบบที่ติดตั้งในฝั่งลูกค้า (customer-local) ทำหน้าที่รับไฟล์จากต้นทาง แปลงเป็น Structured Data ที่มี schema ชัดเจน แล้ว project ต่อเป็น Structured Context (Entity / Fact / Relationship / Evidence) สำหรับให้ agent และแอปพลิเคชันปลายทางเรียกใช้
สิ่งที่ระบบนี้เป็นเจ้าของ
- Source configuration แบบ immutable + versioned
- ผลการสกัดไฟล์และผล AI ต่อไฟล์
- Dataset / SchemaVersion / Record ทุกเวอร์ชัน
- Governance ของการตรวจทาน (Submission, Candidate, Approval)
- Canonical Context และ ContextVersion
- Readiness ของ deployment
สิ่งที่ระบบนี้ไม่ได้เป็นเจ้าของ
- ไฟล์ดิบ — เป็นของ File Ingestion ภายนอก
- ความลับ — อยู่ใน AWS Secrets Manager
- ตัวตนผู้ใช้ OpenWebUI — เป็นของ OpenWebUI
- Public TLS / DNS — เป็นของ Cloudflare Tunnel
ช่องทางเข้าข้อมูลที่ใช้งานจริงวันนี้
FILE_READY— ดึงจาก File Ingestion (pull, inventory + event replay)OPENWEBUI_UPLOAD— ผู้ใช้อัปโหลดตรงผ่าน Service JWT (push)POST /api/v1/ingestion/file-ready— ต้นทาง push แจ้งไฟล์พร้อม
Django applications
| App | ขอบเขต | ฐานข้อมูล (router) | Migrations |
|---|---|---|---|
coreth_context |
Source, signal, extraction, semantic, canonical Context, provider API, doctor, backup | default | 0001–0017 |
coreth_data |
Dataset, SchemaVersion, Record, governance/review, schema design, OpenWebUI archive | data ถ้ามี มิฉะนั้น default | 0001–0018 |
coreth_control |
EffectivePolicy, PolicyOverride, WorkAdmission, Admin Monitor UI, AdminMembership | default | 0001–0006 |
การแยกนี้บังคับด้วย config/database_router.py → DomainRouter
ซึ่ง route ทุก model ของ coreth_data ไป alias data เมื่อมี
DATA_DATABASE_URL และ route ที่เหลือ (รวม django.contrib.auth) ไป default
สถานะของฟีเจอร์ในเอกสารนี้
2. Architecture#
ทุกกล่องและทุกลูกศรในหัวข้อนี้ตรวจกลับได้กับไฟล์จริงใน repository ถ้า component ไม่ปรากฏในโค้ด จะไม่อยู่ในไดอะแกรม
2.1 System Context#
ขอบเขตของระบบ: อะไรอยู่ใน Compose stack ของลูกค้า และอะไรเป็นบริการภายนอกที่ต้องพึ่งพา
compose.yaml, compose.cloudflare.yaml, deploy/Caddyfile*,
config/settings.py, coreth_context/file_ingestion.py,
coreth_context/secrets.py, coreth_data/snapshots.py, coreth_control/logs.py
api ไม่ได้ publish port ออกนอก container
ทางเข้าเดียวคือ caddy ที่ 127.0.0.1:8080 ส่วน admin
publish 127.0.0.1:8081 แยกต่างหาก และ deploy/Caddyfile (โหมดปกติ)
proxy ทุก path ไป api:8000 เท่านั้น — จึงเปิด /admin ผ่าน :8080 ไม่ได้
ต้องใช้ :8081 หรือเปิด overlay Cloudflare ที่ใช้ deploy/Caddyfile.cloudflare
ซึ่ง route /admin* ไป admin:8000
2.2 Runtime / Deployment Topology#
ทุก service มาจาก compose.yaml (และ compose.cloudflare.yaml สำหรับ overlay)
service ที่ไม่มี profile จะขึ้นเสมอเมื่อสั่ง docker compose up
| Service | Profile | คำสั่งที่รันจริง | Port ที่เปิด | Networks | สถานะ |
|---|---|---|---|---|---|
setup | — (ขึ้นเสมอ) | python -m coreth_context.compose_setup --auto |
— | none | IMPLEMENTED |
migrate | — (ขึ้นเสมอ) | manage.py migrate_domains |
— | backend, egress | IMPLEMENTED |
api | — (ขึ้นเสมอ) | gunicorn config.wsgi --workers 2 --timeout 120 |
ไม่เปิด | backend, egress | IMPLEMENTED |
caddy | — (ขึ้นเสมอ) | caddy:2.10-alpine + deploy/Caddyfile |
127.0.0.1:${CORETH_HTTP_PORT:-8080} | frontend, backend | IMPLEMENTED |
postgres | local |
postgres:17-alpine |
— | backend | OPTIONAL |
admin | admin, local |
gunicorn + CORETH_ADMIN_ENABLED=true |
127.0.0.1:${CORETH_ADMIN_PORT:-8081} | backend, egress | IMPLEMENTED |
worker | worker |
manage.py runworker |
— | backend, egress | IMPLEMENTED |
processor | worker |
manage.py runworker --process-only |
— | backend, egress | OPTIONAL |
backup | ops |
manage.py runbackup_scheduler |
— | backend, egress | OPTIONAL |
ops | ops |
ฐานสำหรับ docker compose run --rm ops … (command ที่ประกาศไว้เป็น context_doctor --help) |
— | backend, egress | OPTIONAL |
wizard | wizard |
compose_setup โหมดโต้ตอบ · mount .:/setup · user 0:0 |
— | backend, egress | IMPLEMENTED |
test | test |
pytest -q (build arg INSTALL_DEV=true) |
— | backend, egress | OPTIONAL |
cloudflare-origin | cloudflare |
caddy + deploy/Caddyfile.cloudflare |
— | backend, tunnel | OPTIONAL |
cloudflared | cloudflare |
tunnel --no-autoupdate run |
— | tunnel, egress | OPTIONAL |
backend ถูกประกาศ internal: true — container ที่อยู่เฉพาะ backend
ออกอินเทอร์เน็ตไม่ได้ ทุก service ที่ต้องเรียก AWS/OpenAI/File Ingestion จึงถูกต่อกับ
egress ด้วย (ดู networks: ของแต่ละ service ใน compose.yaml)
Secret injection ตอน container เริ่มทำงาน
Dockerfile ตั้ง ENTRYPOINT ["python","-m","coreth_context.runtime_secrets"]
ทุกคำสั่งจึงถูกห่อด้วย launcher ที่ resolve OPENAI_API_KEY_REF
(ต้องขึ้นต้น aws-secretsmanager://) เข้าสู่ os.environ["OPENAI_API_KEY"]
แล้วค่อย execvp คำสั่งจริง ถ้า resolve ไม่สำเร็จ process จบด้วย exit code 78
และไม่พิมพ์ค่า/reference ออก log
OPENAI_API_KEY กับ OPENAI_API_KEY_REF พร้อมกันไม่ได้ — launcher จะปฏิเสธ
ส่วน secret ของ connector, webhook, backup key และ File Ingestion reader จะ resolve
เมื่อต้องใช้จริงผ่าน AwsSecretsManagerResolver ไม่ถูกเขียนลงฐานข้อมูลหรือ log
2.3 Main Data Flow#
เส้นทางจริงจากไฟล์ต้นทางจนถึง canonical Context ทุกขั้นขับเคลื่อนโดย
runworker ซึ่งเรียก WorkerCycle.run_once() วนทุก
WORKER_POLL_SECONDS (default 2 วินาที)
WorkerCycle.run_once(): poll ทุก source → process_pending →
project DatasetChangedEvent ที่ยัง PENDING → run_review_jobs() →
ContextDoctor → OutboxDispatcher
สิ่งที่ worker ทำในหนึ่งรอบ
| # | งาน | โค้ด | ผลที่นับใน output JSON |
|---|---|---|---|
| 1 | poll ทุก source ล่าสุด (File Ingestion + connector) | poll_file_ingestion(), synchronizer.poll_source() | polled |
| 2 | reconcile เมื่อครบรอบ | reconcile_source() | reconciled |
| 3 | ประมวลผล signal ที่ค้าง | process_pending(limit=batch_size) | processed |
| 4 | project dataset event → Context | ContextProjector.project_event() | projected |
| 5 | รัน review job ที่ค้าง | coreth_data.governance.run_review_jobs() | review_jobs |
| 6 | ประเมิน readiness ของทุก customer | ContextDoctor().evaluate(persist=True) | — (เขียน CustomerContextReadiness) |
| 7 | ส่ง Context Changed outbox | OutboxDispatcher.dispatch_one() | dispatched |
/health/ready อ่านค่า
CustomerContextReadiness ซึ่งเขียนโดย ContextDoctor
ที่รันอยู่ใน runworker เท่านั้น ถ้าไม่สั่ง
docker compose up -d worker ระบบจะไม่มีวันขึ้น READY
และจะตอบ 503 DOCTOR_NOT_EVALUATED ตลอด
ReviewJob ทุกชนิดที่ worker รันได้
kind | ทำอะไร | ถูกสร้างเมื่อ |
|---|---|---|
ANALYZE | analyze_submission() — วิเคราะห์ไฟล์ที่อัปโหลดด้วย File Agent | มี Submission ใหม่ที่ยังไม่มี candidate |
APPLY (ค่าเริ่มต้น) | apply_dataset(target_id) — เผยแพร่กลุ่มที่ยืนยันแล้ว | คนกดยืนยัน / อนุมัติ schema |
AUTO_NEW | apply_dataset(auto_group_ids=[group]) — เฉพาะแถวใหม่ที่ผ่านนโยบาย | schedule_group() เมื่อเปิด auto-approve |
BATCH_REVIEW | run_batch() — ตรวจหลาย submission พร้อมกันต่อศูนย์ | สร้าง batch ที่ /admin/review-batches |
FEEDBACK / FEEDBACK_APPLY | run_feedback_job() — ให้ AI แก้ข้อเสนอ schema ตาม comment | POST /api/v1/schema-improvements/{id}/feedback |
หลังจบทุก job รอบหนึ่ง run_review_jobs() จะ mirror GovernanceAudit
ไป OperatorActionAudit, เรียก reconcile_openwebui_history()
และ reconcile_openwebui_archives() ต่อทันที
เมื่อ CORETH_REVIEW_PAUSE_APPLY=true ฟังก์ชันนี้จะ return ทันทีโดยไม่รันอะไรเลย
2.4 Submission / Review Flow#
เส้นทางนี้ใช้ทั้งไฟล์ที่มาจาก File Ingestion และไฟล์ที่ผู้ใช้อัปโหลดตรงจาก OpenWebUI
ต่างกันที่ว่า OpenWebUI สร้าง Submission ผ่าน API โดยตรง ขณะที่ FILE_READY
สร้างจาก SourceSignal ในรอบของ worker
coreth_data/review_api.py, coreth_data/governance.py
(stage_submission, confirm_submission, apply_dataset,
publish_review_group), coreth_data/new_data.py
API ของ workflow นี้
| Method + path | ใครเรียกได้ | ผล |
|---|---|---|
GET /api/v1/submissions | session หรือ Service JWT (submissions:read) | รายการ submission ล่าสุด 100 รายการ |
POST /api/v1/submissions | Service JWT (submissions:write) หรือ session | สร้าง Submission + ReviewJob(ANALYZE) |
GET /api/v1/submissions/{id} | เหมือนข้างบน | สรุป submission |
GET /api/v1/submissions/{id}/review | เหมือนข้างบน | manifest ของแถวที่รอตรวจ + digest |
PATCH /api/v1/submissions/{id}/candidate | เหมือนข้างบน | แก้ค่าแถว → เพิ่ม revision |
POST /api/v1/submissions/{id}/confirm | session ของคนเท่านั้น (HUMAN_SESSION_REQUIRED) | ReviewReceipt + ReviewJob(APPLY) |
GET/PATCH/POST /api/v1/openwebui/submissions… | Service JWT ของ OpenWebUI | มุมมองเฉพาะผู้ใช้ OpenWebUI คนนั้น |
POST /api/v1/data-conflicts/{id}/resolve | session | ตัดสิน DataConflict ที่ยัง OPEN |
GET /api/v1/submissions-openapi.json | session / Service JWT | OpenAPI ให้ OpenWebUI ใช้เป็น tool |
นโยบายอนุมัติที่ทำงานจริง
| กรณี | ต้องมีคนยืนยันไหม | เงื่อนไขในโค้ด |
|---|---|---|
ไฟล์จาก OpenWebUI (มี OpenWebUIUploadReceipt หรือ actor ขึ้นต้น openwebui:) |
ต้องมีเสมอ | new_data.manual_origin() → HUMAN_UPLOAD_CONFIRMATION_REQUIRED |
| แถว ใหม่ จาก FILE_READY ที่ผ่านนโยบาย | ไม่ต้อง | CORETH_AUTO_APPROVE_NEW_DATA=true + structured_data.auto_approve_new_data + ไม่ pause |
| แถวที่ แก้ค่าเดิม หรือมีค่าขัดแย้ง | ต้องมี | classify_rows() ปล่อยเฉพาะ kind new เข้า auto path |
| Dataset ใหม่ (ยังไม่มี schema) | ต้องมี | ต้องอนุมัติ SchemaImprovement ก่อนเสมอ |
เปิด CORETH_REVIEW_PAUSE_APPLY=true |
run_review_jobs() return ทันที — ไม่มีการเผยแพร่ใด ๆ จนกว่าจะปิด flag |
|
revision และ manifest_digest
ถ้า candidate เปลี่ยนระหว่างที่คนกำลังตรวจ API จะตอบ 409 REVISION_CONFLICT
ส่วน RowApproval ผูกกับ row_digest + policy_digest
ถ้านโยบายหรือแถวเปลี่ยน จะได้ ROW_APPROVAL_STALE และไม่เผยแพร่
ความขัดแย้งของข้อมูล (DataConflict)
เมื่อสองแหล่งเสนอค่าต่างกันในฟิลด์เดียวกันของ record เดียวกัน ระบบสร้าง
DataConflict (unique ที่ dataset + record_key + field) สถานะ OPEN
แถวนั้นจะไม่ถูกเผยแพร่จนกว่าคนจะตัดสินที่ /admin/data-conflicts
หรือเรียก POST /api/v1/data-conflicts/{id}/resolve
ลำดับความน่าเชื่อถือกำหนดด้วย SourceAuthorityPolicy
(สร้างด้วย manage.py configure_source_authority)
2.5 Schema Improvement Flow#
SchemaImprovement คือข้อเสนอเปลี่ยนสัญญาโครงสร้างข้อมูลหนึ่งชุด
มี unique constraint ที่ customer + environment + concept_key
จึงมีข้อเสนอที่เปิดอยู่ได้เพียงหนึ่งต่อหนึ่งแนวคิด
coreth_data/governance.py::decide_schema(),
coreth_data/schema_evolution.py, coreth_data/schema_feedback.py,
coreth_data/schema_design_plans.py
อนุมัติอัตโนมัติได้แค่ไหน
schema_evolution.enabled() จะยอมให้อนุมัติอัตโนมัติก็ต่อเมื่อครบทุกข้อพร้อมกัน:
CORETH_AUTO_APPROVE_ADDITIVE_SCHEMA=true(ค่าเริ่มต้นtrue)CORETH_REVIEW_PAUSE_APPLY=false- source มี
structured_data.auto_approve_additiveไม่ถูกตั้งเป็นfalse semantic_processing.modeเป็นAI_REQUIREDหรือAI_OPTIONALsemantic_processing.allow_external_model = true- EffectivePolicy มี
ai.enabled = true
ADDITIVE
คือ เพิ่ม field ใหม่ที่ไม่บังคับกรอกเท่านั้น
(classify() คืน MODIFY หรือ UNSAFE_ADDITION เมื่อไม่ใช่)
การแก้ชนิดข้อมูล ลบ field เปลี่ยน identity หรือสร้าง dataset ใหม่ ต้องให้คนอนุมัติเสมอ
Schema Design (ออกแบบ schema ล่วงหน้าด้วย AI)
นอกจากข้อเสนอที่เกิดจากไฟล์จริง ยังมีเส้นทางออกแบบล่วงหน้า:
POST /api/v1/schema-design รับไฟล์ตัวอย่าง/คำอธิบายแล้วสร้าง
SchemaDesignPlan + SchemaDesignSource ให้ตรวจที่
/admin/schema-improvements เมื่อ source มี design ที่อนุมัติแล้ว
(approved_designs()) การประมวลผลไฟล์จะยึด design นั้นแทนการเลือก field แบบเดิม
IMPLEMENTED
2.6 Storage / Source-of-Truth Map#
แต่ละโดเมนมีเจ้าของเดียว อย่าเขียนข้ามโดเมนโดยตรง
| โดเมน | เจ้าของ | Canonical store | Derived / mirror |
|---|---|---|---|
| ไฟล์ดิบ + file version | File Ingestion (ภายนอก) | S3 ของ File Ingestion + /v1/file-versions/{id} |
TrustedArtifactReference, SourceObject |
| ความลับทุกชนิด | AWS Secrets Manager (ภายนอก) | aws-secretsmanager://<secret-id> |
เก็บเฉพาะ reference ใน SourceIdentity.secret_ref และ .env |
| Source configuration | coreth_context |
SourceIdentity (immutable ต่อ config_version) |
latest_source_configurations(), ArchivedSource |
| คิวงานนำเข้า | coreth_context |
SourceSignal, SourceCheckpoint, FileIngestionCursor |
SourceSyncState, IngestionHistory |
| ผลสกัดไฟล์ / ผล AI | coreth_context |
ExtractionRecord, SemanticProcessingRecord |
ไฟล์ใน volume record-artifacts เมื่อ CORETH_COMPACT_RECORDS=true |
| สัญญา schema | coreth_data |
SchemaVersion + Dataset.active_schema |
ตารางจริงใน schema app_output |
| แถวข้อมูล | coreth_data |
Record + RecordVersion (ทุกเวอร์ชัน) |
app_output.<dataset>, DatasetVersion, TransformRun |
| Governance การตรวจทาน | coreth_data |
Submission, CandidateGroup, CandidateRow, ReviewReceipt, RowApproval |
GovernanceAudit → OperatorActionAudit |
| Canonical Context | coreth_context |
Entity, Fact, Relationship, Evidence, ContextVersion |
ContextSliceRecord, CanonicalContextState |
| นโยบายการทำงาน | coreth_control |
EffectivePolicy (versioned) + PolicyOverride |
WorkAdmissionState, WorkAdmissionLease |
| ผู้ใช้ Admin | Django auth + coreth_control |
auth_user + AdminMembership (customer + environment + role) |
AdminBranding |
| ความพร้อมใช้งาน | coreth_context |
CustomerContextReadiness, DoctorEvaluation |
GET /health/ready, ServiceHeartbeat |
| สำรองข้อมูล | coreth_context |
ไฟล์เข้ารหัสใน CORETH_BACKUP_DIRECTORY (volume backup-data) |
BackupArtifact, RestoreRehearsal |
| Replay snapshot | coreth_data |
S3 CORETH_SNAPSHOT_BUCKET + SSE-KMS |
InputSnapshotArtifact |
| สิทธิ์ของ tool ภายนอก | coreth_context |
ToolServiceGrant (scopes + actions + expiry) |
ProviderAccessAudit |
DATA_DATABASE_URL ทุก app จะใช้ฐานเดียวกัน
(DomainRouter.data_database() คืน "default")
ถ้าตั้ง จะมีสอง connection แยกกันจริง — จึง ไม่มี transaction เดียวที่คร่อมสองฐาน
การส่งข้อมูลจาก data → context ทำผ่าน outbox DatasetChangedEvent เท่านั้น
2.7 Component Responsibilities#
api IMPLEMENTED
- หน้าที่
- REST สำหรับ ingress, provider, data, submission และ health
- Input
- HTTP ผ่าน caddy · Service JWT หรือ Django session
- Output
- JSON · เขียน Submission / TrustedArtifactReference / SourceSignal
- เป็นเจ้าของ
- ไม่มี state เฉพาะตัว — เขียนลงฐานร่วม
- ต้องพึ่ง
- PostgreSQL, Secrets Manager (เมื่อ verify File Ingestion)
worker IMPLEMENTED
- หน้าที่
- poll · process · project · review jobs · doctor · outbox
- Input
SourceSignal,ReviewJob,DatasetChangedEvent- Output
- Dataset/Record, canonical Context,
ContextChangedEvent - เป็นเจ้าของ
ServiceHeartbeat(worker)และCustomerContextReadiness- ต้องพึ่ง
- PostgreSQL, Secrets Manager, OpenAI, File Ingestion, volume
record-artifacts
admin (Monitor) IMPLEMENTED
- หน้าที่
- ตั้งค่า source, ตรวจทานข้อมูล/โครงสร้าง, สำรวจข้อมูล, จัดการผู้ใช้
- Input
- Django session +
AdminMembership - Output
- เขียน source config, ตัดสิน schema/conflict, สั่ง retry/recover
- เป็นเจ้าของ
AdminBranding,OperatorActionAudit- ต้องพึ่ง
CORETH_ADMIN_ENABLED=true· CloudWatch Logs (ไม่บังคับ)
caddy IMPLEMENTED
- หน้าที่
- reverse proxy + security headers (
X-Frame-Options DENY,nosniff) - Input
127.0.0.1:8080เท่านั้น ไม่ผูก 0.0.0.0- Output
- ส่งต่อ
api:8000(และadmin:8000ใน overlay) - เป็นเจ้าของ
- ไม่มี state
migrate IMPLEMENTED
- หน้าที่
migrate_domains= migrate ทุก alias แล้วรันsecure_database- Output
- ตาราง + เปิด RLS + ถอนสิทธิ์
PUBLIC/anon/authenticated - รันเมื่อ
- ก่อน
api/workerทุกครั้ง (service_completed_successfully)
backup OPTIONAL
- หน้าที่
runbackup_scheduler— สำรองเข้ารหัส + ซ้อมกู้คืนตามรอบ- Output
- ไฟล์
CORETHBK1(AES-GCM) +BackupArtifact+RestoreRehearsal - ต้องพึ่ง
CORETH_BACKUP_KEY_FILEหรือCORETH_BACKUP_KEY_SECRET_REF,pg_dump
Connector ที่มีในโค้ด
source_type | Adapter | ตั้งค่าได้ที่ | สถานะ |
|---|---|---|---|
FILE_READY | connectors/file_ready.py + file_ingestion.py | Admin /admin/setup และ CLI | IMPLEMENTED |
OPENWEBUI_UPLOAD | connectors/openwebui_upload.py | Admin /admin/setup และ CLI | IMPLEMENTED |
S3 | connectors/s3.py | manage.py configure_source เท่านั้น | CLI-ONLY |
GOOGLE_DRIVE | connectors/drive.py | manage.py configure_source เท่านั้น | CLI-ONLY |
POSTGRESQL / MYSQL / MARIADB | connectors/sql.py (read-only, SQLAlchemy) | manage.py configure_source เท่านั้น | CLI-ONLY |
REST | connectors/rest.py | manage.py configure_source เท่านั้น | CLI-ONLY |
CORETH_AGENTIC_ONLY=true (ค่าเริ่มต้น) signal ของ source ที่
semantic_processing.mode ไม่ใช่ AI_REQUIRED จะถูกปิดด้วย error
AGENTIC_REQUIRED ทันที — connector กลุ่ม CLI-ONLY ข้างบนจึงใช้งานไม่ได้จริง
จนกว่าจะตั้ง source ให้เป็น AI_REQUIRED หรือปิด CORETH_AGENTIC_ONLY
ระหว่างย้ายระบบ
3. Prerequisites#
ตรวจให้ครบก่อนเริ่ม ทุกข้อในตารางนี้มาจากสิ่งที่โค้ดเรียกใช้จริง
| สิ่งที่ต้องมี | บังคับ? | ทำไม |
|---|---|---|
| Docker Engine + Compose plugin v2 | บังคับ | ทั้งระบบรันผ่าน compose.yaml และใช้ profiles, extends, depends_on.condition |
| Git | บังคับ | ต้อง clone repo ลงเครื่องเพราะ compose ใช้ build: . |
| Python 3 | บังคับ | สคริปต์ในคู่มือนี้ใช้ python3 สร้าง UUID · ประกอบ JSON ของ secret · และอ่านผล JSON จาก management command (Ubuntu 22.04+ มีติดตั้งมาแล้ว) |
PostgreSQL 17 (หรือใช้ profile local) | บังคับ | secure_database ใช้ RLS และ app_output schema; SQLite ใช้ได้เฉพาะตอนพัฒนา |
| Customer UUID | บังคับ | CORETH_DEPLOYMENT_CUSTOMER_ID — /health/ready ตอบ CUSTOMER_NOT_CONFIGURED ถ้าไม่มี |
| AWS credentials (IAM role หรือ key) | บังคับ | AwsSecretsManagerResolver เป็นทางเดียวที่ระบบอ่านความลับ |
| OpenAI API key เก็บใน Secrets Manager | บังคับ | CORETH_AGENTIC_ONLY=true ทำให้ทุก signal ต้องผ่าน AI |
| File Ingestion base URL + reader secret | ถ้าใช้ FILE_READY | FileIngestionClient ต้องมี base_url + secret_ref |
| S3 bucket + KMS key | ไม่บังคับ | เฉพาะ replay snapshot (CORETH_SNAPSHOT_BUCKET) ซึ่งบังคับใช้ SSE-KMS |
| Cloudflare Tunnel | ไม่บังคับ | เปิด Admin ผ่าน HTTPS สาธารณะโดยไม่เปิด port บนเครื่อง |
| CloudWatch log group | ไม่บังคับ | หน้า /admin/activity อ่าน log ผ่าน filter_log_events |
3.1 ตรวจสิ่งที่ต้องมีด้วยสคริปต์เดียว
คัดลอกทั้งบล็อกไปวางใน shell บนเครื่องที่จะติดตั้ง ผลลัพธ์จะเป็น PASS / WARN / FAIL ต่อบรรทัด
# ตรวจเครื่องมือและสิทธิ์ที่ CORETH Context Processor ต้องใช้
AWS_REGION="${AWS_REGION:-ap-southeast-1}"
ok(){ printf 'PASS %-26s %s\n' "$1" "$2"; }
wn(){ printf 'WARN %-26s %s\n' "$1" "$2"; }
no(){ printf 'FAIL %-26s %s\n' "$1" "$2"; }
command -v git >/dev/null && ok git "$(git --version)" || no git "ยังไม่ได้ติดตั้ง"
command -v docker >/dev/null && ok docker "$(docker --version)" || no docker "ยังไม่ได้ติดตั้ง"
command -v python3 >/dev/null && ok python3 "$(python3 -V 2>&1)" || no python3 "ยังไม่ได้ติดตั้ง (คู่มือนี้ใช้ในหลายขั้นตอน)"
docker compose version >/dev/null 2>&1 \
&& ok "docker compose" "$(docker compose version --short)" \
|| no "docker compose" "ต้องใช้ Compose plugin v2"
docker info >/dev/null 2>&1 && ok "docker daemon" "เข้าถึงได้" || no "docker daemon" "ผู้ใช้ปัจจุบันเรียก docker ไม่ได้"
if command -v aws >/dev/null; then
ok "aws cli" "$(aws --version 2>&1 | cut -d' ' -f1)"
ARN=$(aws sts get-caller-identity --query Arn --output text 2>/dev/null) \
&& ok "aws sts" "$ARN" || no "aws sts" "ไม่มี credential ที่ใช้ได้"
aws secretsmanager list-secrets --max-results 1 --region "$AWS_REGION" >/dev/null 2>&1 \
&& ok "secretsmanager" "อ่านรายการ secret ได้ใน $AWS_REGION" \
|| no "secretsmanager" "ไม่มีสิทธิ์ list/GetSecretValue ใน $AWS_REGION"
else
wn "aws cli" "ไม่มีบนเครื่องนี้ (container ใช้ IAM role ได้โดยไม่ต้องมี cli)"
fi
FREE=$(df -Pm . | awk 'NR==2{print $4}')
[ "$FREE" -ge 20480 ] && ok disk "เหลือ ${FREE} MB" || wn disk "เหลือ ${FREE} MB (แนะนำอย่างน้อย 20 GB)"
getent hosts api.openai.com >/dev/null 2>&1 && ok dns "api.openai.com แปลงชื่อได้" || wn dns "แปลงชื่อ api.openai.com ไม่ได้"
ผลลัพธ์ที่ควรเห็น
สิทธิ์ IAM ขั้นต่ำที่ container ต้องมี (ถอดจากทุกจุดที่เรียก boto3)
| Action | Resource | ใช้ที่ | บังคับ? |
|---|---|---|---|
secretsmanager:GetSecretValue | ARN ของ secret ที่อ้างถึงทุกตัว | coreth_context/secrets.py | บังคับ |
kms:Decrypt | KMS key ที่เข้ารหัส secret | ผ่าน Secrets Manager | ถ้าใช้ CMK |
s3:GetObject, s3:GetObjectVersion | bucket ของ File Ingestion / S3 source | connectors/factory.py | ถ้าใช้ FILE_READY/S3 |
s3:PutObject, s3:DeleteObject, s3:GetObject | CORETH_SNAPSHOT_BUCKET | coreth_data/snapshots.py | ไม่บังคับ |
kms:GenerateDataKey, kms:Decrypt | CORETH_SNAPSHOT_KMS_KEY_ID | SSE-KMS ของ snapshot | ไม่บังคับ |
logs:FilterLogEvents | CORETH_ADMIN_LOG_GROUP | coreth_control/logs.py | ไม่บังคับ |
ระบบไม่เรียก secretsmanager:CreateSecret เอง — การสร้าง secret เป็นงานของผู้ติดตั้ง
และควรใช้ identity คนละตัวกับ role ของ container
4. Install#
ทำตามลำดับ แต่ละขั้นมีคำสั่งเดียวและผลลัพธ์ที่ต้องเห็นก่อนไปขั้นถัดไป
coreth_context/operator_prompt.py เมื่อรันจาก terminal จริง (มี TTY)
และยังไม่ได้ส่ง flag มันจะ ดึงค่าจริงจากฐานข้อมูลมาเป็นเมนูตัวเลข ให้เลือก
ถ้ามีตัวเลือกเดียวจะเลือกให้อัตโนมัติพร้อมบอกว่าเลือกอะไร และถ้ามีค่าใน .env อยู่แล้ว
จะนำมาใช้ซ้ำโดยไม่ถาม จึงไม่ต้องจำ UUID เอง
STEP 1 Clone repository
Compose ใช้ build: . จึงต้องมีซอร์สอยู่บนเครื่อง
sudo mkdir -p /opt/coreth && sudo chown "$USER":"$USER" /opt/coreth
git clone git@github.com:Core-technology-Co-Ltd/ai-coreth-context-processor.git /opt/coreth/app
cd /opt/coreth/app && git rev-parse --short HEAD
ผลที่ต้องเห็น
STEP 2 เตรียม secret ของ OpenAI ใน Secrets Manager
โค้ดอ่าน API key จาก secret ที่เป็น JSON object และต้องมี key ชื่อ
OPENAI_API_KEY (ดู semantic/credentials.py::resolve_api_key)
สคริปต์นี้แสดง secret ที่มีอยู่ให้เลือกก่อน ถ้ายังไม่มีจึงสร้างใหม่โดยไม่แสดง key บนจอ
AWS_REGION="${AWS_REGION:-ap-southeast-1}"
# ---- ตัวช่วยเลือกจากรายการจริง ใช้ซ้ำได้ในขั้นตอนอื่น ----
select_one() {
local title="$1" i=1 line n; local -a items=()
while IFS= read -r line; do [ -n "$line" ] && items+=("$line"); done
printf '\n%s\n' "$title"
if [ "${#items[@]}" -eq 0 ]; then
printf ' (ยังไม่มีข้อมูลในระบบ)\n'; read -r -p ' พิมพ์ค่าเอง: ' SELECTED </dev/tty; return
fi
for line in "${items[@]}"; do printf ' %d. %s\n' "$i" "$line"; i=$((i+1)); done
if [ "${#items[@]}" -eq 1 ]; then
SELECTED="${items[0]}"; printf ' เลือกอัตโนมัติ: %s\n' "$SELECTED"; return
fi
while :; do
read -r -p " เลือก [1-${#items[@]}]: " n </dev/tty
case "$n" in ''|*[!0-9]*) ;; *)
[ "$n" -ge 1 ] && [ "$n" -le "${#items[@]}" ] && { SELECTED="${items[$((n-1))]}"; return; };;
esac
done
}
# ---- 1) เลือก secret ที่มีอยู่ หรือสร้างใหม่ ----
NEW_LABEL='+ สร้าง secret ใหม่'
select_one "AWS Secrets Manager — เลือก secret ของ OpenAI" < <(
aws secretsmanager list-secrets --region "$AWS_REGION" \
--query 'SecretList[].Name' --output text 2>/dev/null | tr '\t' '\n' | sort
printf '%s\n' "$NEW_LABEL"
)
# ---- 2) ถ้าเลือก "สร้างใหม่" ให้ถามชื่อและ key โดยไม่ echo ค่า ----
if [ "$SELECTED" = "$NEW_LABEL" ]; then
read -r -p ' ชื่อ secret ใหม่ (เช่น org/production/openai): ' SELECTED </dev/tty
read -rs -p ' OpenAI API key (ไม่แสดงบนจอ): ' OPENAI_KEY </dev/tty; echo
aws secretsmanager create-secret --region "$AWS_REGION" --name "$SELECTED" \
--secret-string "$(OPENAI_KEY="$OPENAI_KEY" python3 -c \
'import json,os;print(json.dumps({"OPENAI_API_KEY":os.environ["OPENAI_KEY"]}))')" >/dev/null
unset OPENAI_KEY
fi
# ---- 3) ยืนยันว่า secret ที่เลือกมี key OPENAI_API_KEY จริง (ไม่พิมพ์ค่า) ----
if aws secretsmanager get-secret-value --region "$AWS_REGION" --secret-id "$SELECTED" \
--query SecretString --output text 2>/dev/null \
| python3 -c 'import json,sys
try: d=json.load(sys.stdin)
except Exception: sys.exit(1)
sys.exit(0 if isinstance(d,dict) and str(d.get("OPENAI_API_KEY","")).strip() else 1)'
then
echo "PASS $SELECTED มี key OPENAI_API_KEY"
else
echo "FAIL $SELECTED ไม่มี key OPENAI_API_KEY ที่ใช้ได้ — แก้ก่อนไปขั้นถัดไป"
exit 1
fi
CORETH_AI_SECRET_REF="aws-secretsmanager://$SELECTED"
echo "CORETH_AI_SECRET_REF=$CORETH_AI_SECRET_REF"
ผลที่ต้องเห็น
.env หรือส่งเป็น --secret-string แบบพิมพ์ตรง ๆ
normalize_secret_reference() ปฏิเสธค่าที่ขึ้นต้นด้วย sk- อยู่แล้ว
ขั้นที่ 3 ของสคริปต์ตรวจเฉพาะว่า มี key OPENAI_API_KEY ที่ไม่ว่าง
ไม่ได้ยืนยันว่า key ใช้เรียก OpenAI ได้จริง — wizard ในขั้นที่ 4 จะโหลดรายการโมเดลจริง
ซึ่งเป็นการยืนยันการเข้าถึง
STEP 3 หา Customer UUID
ถ้าเป็นการติดตั้งใหม่ ให้สร้าง UUID ขึ้นมาหนึ่งค่าและใช้ค่านี้ตลอดอายุ deployment ถ้าเป็นการติดตั้งซ้ำ/ย้ายเครื่อง ให้ดึงจากระบบเดิม
ติดตั้งใหม่
มีระบบเดิมอยู่แล้ว — ดึงค่าจริงมาเลือก
# list_source_policies --all-customers คืน JSON ของทุก source พร้อม customer_id
docker compose exec -T api python manage.py list_source_policies --all-customers \
| python3 -c 'import json,sys
d=json.load(sys.stdin)
for c in sorted({s["customer_id"] for s in d["sources"]}): print(c)'
ผลที่ต้องเห็น
.env เป็น CORETH_DEPLOYMENT_CUSTOMER_ID
หลังจากนั้นทุกคำสั่งที่ต้องใช้ customer จะ หยิบค่านี้ให้อัตโนมัติ
(operator_fields.customer() อ่านจาก settings.CORETH_OPERATIONS)
จึงไม่ต้องพิมพ์ --customer-id อีกเลย
STEP 4 รัน setup wizard
Wizard เขียน .env แบบ atomic สิทธิ์ 600 ทดสอบการต่อฐานข้อมูลจริง
และดึงรายชื่อ model จาก OpenAI มาให้เลือกเป็นเมนูตัวเลข
บทสนทนาที่จะเจอ
| คำตอบเรื่องฐานข้อมูล | ผลที่ wizard เขียน |
|---|---|
Context local + Data same | ฐานเดียวใน Compose · เพิ่ม profile local · CONTEXT_DATABASE_URL และ DATA_DATABASE_URL ว่าง |
Context external + Data same | ตั้ง CONTEXT_DATABASE_URL · ไม่เปิด profile local |
Context external + Data external | ตั้งทั้งสองตัวแปร · แยกสองฐานจริง · router เปิด alias data |
Context local + Data external | Compose PostgreSQL สำหรับ context · Data ชี้ภายนอก |
worker ลงใน COMPOSE_PROFILES โดยเจตนา
เพื่อไม่ให้การ deploy เว็บตามปกติไปรีสตาร์ต ingestion ที่กำลังทำงานอยู่
(ดูคอมเมนต์ใน compose.yaml และ tunnel_preflight.EXCLUDED_PROFILES)
STEP 5 Build และเริ่ม service ฝั่งเว็บ
คำสั่งนี้รัน setup → migrate (migrate ทุกฐาน + เปิด RLS) → api → caddy และ postgres/admin ตาม profile
ผลที่ต้องเห็น
ตรวจว่า migration ทำงานครบและ RLS ถูกเปิด:
STEP 6 ตรวจ liveness ผ่าน Caddy
api ไม่เปิด port เอง ทางเข้าคือ caddy ที่ 127.0.0.1:8080
/health/live ตอบ ready:false เสมอโดยเจตนา — เป็น liveness probe
ของ process เท่านั้น ความพร้อมจริงอยู่ที่ /health/ready ซึ่งจะยังไม่ผ่าน
จนกว่าจะเริ่ม worker ในขั้นที่ 8
STEP 7 สร้างผู้ดูแลระบบคนแรก
bootstrap_root_user สร้าง Django superuser พร้อม AdminMembership
บทบาท ADMIN ผูกกับ customer + environment ของ deployment นี้
(ถ้าไม่ส่ง flag จะใช้ค่าจาก .env ให้อัตโนมัติ)
ผลที่ต้องเห็น
เปลี่ยนรหัสผ่านภายหลังด้วย docker compose exec admin python manage.py changepassword root
STEP 8 เริ่ม worker
worker คือสิ่งที่ทำให้ระบบ "มีชีวิต" — นำเข้า ประมวลผล project และประเมิน readiness ต้องสั่งแยกเสมอ และควรสั่งตอนที่ไม่มีงานค้างเพื่อไม่ตัดงานที่กำลังทำ
ผลที่ต้องเห็น
worker หรือ * ลงใน COMPOSE_PROFILES
และอย่า deploy ด้วย --profile '*' — การ up ตามปกติจะไป
recreate worker และตัด ingestion ที่กำลังทำงานอยู่กลางคัน
STEP 9 เปิด Admin Monitor
| โหมด | URL | เงื่อนไข |
|---|---|---|
| เฉพาะเครื่อง | http://127.0.0.1:8081/admin/ | profile admin หรือ local |
| ผ่าน Cloudflare | https://<CORETH_PUBLIC_HOST>/admin/ | profile cloudflare + overlay |
| ผ่าน Caddy :8080 | ใช้ไม่ได้ | deploy/Caddyfile ส่งทุก path ไป api ซึ่งไม่เปิด CORETH_ADMIN_ENABLED |
เข้าจากเครื่องอื่นผ่าน SSH tunnel:
STEP 10 (ไม่บังคับ) · เปิด HTTPS สาธารณะด้วย Cloudflare Tunnel
Overlay นี้ไม่เปิด port ใด ๆ บนเครื่อง ทราฟฟิกวิ่งออกจาก cloudflared
ไป Cloudflare แล้วย้อนเข้ามาที่ cloudflare-origin (Caddy อีกตัว)
ซึ่ง route /admin* และ API ของหน้า Admin ไป admin:8000
# 1) สร้าง tunnel + public hostname ใน Cloudflare Zero Trust dashboard
# ตั้ง service ปลายทางเป็น http://cloudflare-origin:8080
# 2) เติมค่าลง .env (hostname เปล่า ๆ ห้ามมี https:// หรือ path)
printf 'COMPOSE_FILE=compose.yaml:compose.cloudflare.yaml\n' >> .env
printf 'CORETH_PUBLIC_HOST=context.example.com\n' >> .env
read -rs -p 'Cloudflare tunnel token: ' T; echo; printf 'CLOUDFLARE_TUNNEL_TOKEN=%s\n' "$T" >> .env; unset T
# เพิ่ม profile cloudflare เข้า COMPOSE_PROFILES ที่มีอยู่ (อย่าลบ local/admin เดิม)
# 3) ตรวจแล้ว deploy
docker compose config --quiet && docker compose up -d --build
ตรวจผล
coreth_context/tunnel_preflight.py จะตรวจสองค่านี้ทุกครั้งที่รัน wizard
และถ้าเคย deploy สำเร็จมาก่อน มันจะ กู้ค่ากลับมาเอง จาก container ที่รันอยู่
(docker inspect coreth-context-processor-cloudflared-1)
จึงไม่ต้องไปหา token ใน dashboard ใหม่
5. Configuration#
การตั้งค่าทั้งหมดผ่านตัวแปรสภาพแวดล้อมใน .env ที่ราก repository
compose.yaml ประกาศ anchor &app-env แล้วทุก service ใช้ซ้ำด้วย *app-env
ตัวแปรที่ไม่ปรากฏใน anchor นั้นจะไม่ถูกส่งเข้า container
5.1 บังคับต้องมี
| ตัวแปร | ทำอะไร | ตัวอย่าง / วิธีหาค่า |
|---|---|---|
CORETH_DEPLOYMENT_CUSTOMER_ID | customer ของ deployment นี้ · /health/ready ใช้ตัวนี้หา readiness · operator command ใช้เป็นค่าเริ่มต้น | wizard ถาม / python3 -c 'import uuid;print(uuid.uuid4())' |
DJANGO_SECRET_KEY | เซ็น session และ CSRF | wizard สุ่มให้ (token_urlsafe(48)) |
POSTGRES_PASSWORD | รหัสของ Compose PostgreSQL และประกอบเป็น DATABASE_URL | wizard สุ่มให้ (token_urlsafe(32)) |
AWS_REGION | region ของ Secrets Manager / S3 / CloudWatch | ap-southeast-1 |
CORETH_ENVIRONMENT | แยกขอบเขต data, grant และ AdminMembership | production |
CORETH_AI_SECRET_REF | secret ที่มี key OPENAI_API_KEY ใช้เป็นค่าเริ่มต้นของ source ใหม่ | aws-secretsmanager://org/production/openai |
CORETH_AI_MODEL | โมเดลเริ่มต้น · wizard ให้เลือกจากรายการจริงของ provider | gpt-4.1-mini |
COMPOSE_PROFILES | เลือก service ที่จะขึ้น (local, admin, cloudflare) | wizard ตั้งให้ · ห้ามใส่ worker หรือ * |
5.2 ฐานข้อมูล
| ตัวแปร | เมื่อว่าง | เมื่อมีค่า |
|---|---|---|
CONTEXT_DATABASE_URL | ใช้ DATABASE_URL ของ Compose (ต้องเปิด profile local) | alias default ชี้ไปฐานภายนอก · conn_max_age=0 |
DATA_DATABASE_URL | ไม่มี alias data → coreth_data ใช้ฐานเดียวกับ context | เปิด alias data แยกจริง · router แยกทุก model ของ coreth_data |
setup_context.check_database() จะต่อจริงด้วย psycopg และ
SELECT 1 ก่อนบันทึก ถ้าต่อไม่ได้ wizard จะไม่เขียน .env
5.3 นโยบายการประมวลผลและอนุมัติ
| ตัวแปร | ค่าเริ่มต้น | ผลจริงในโค้ด |
|---|---|---|
CORETH_AGENTIC_ONLY | true | signal ของ source ที่ไม่ใช่ AI_REQUIRED จะ fail ด้วย AGENTIC_REQUIRED · ปิดเส้นทาง streaming เดิม |
CORETH_FILE_AGENT_ENABLED | true | เปิด File Agent สำหรับ FILE_READY และ OPENWEBUI_UPLOAD |
CORETH_FILE_AGENT_MAX_BYTES | 5242880 | ไฟล์ใหญ่กว่านี้ fail ด้วย AI_FILE_TOO_LARGE (ไม่ตัดทิ้งเงียบ ๆ) |
CORETH_FILE_AGENT_MAX_PAGES | 10 | PDF เกินหน้านี้หรือถูกเข้ารหัส fail ด้วย AI_PDF_LIMIT_OR_ENCRYPTED |
CORETH_AUTO_APPROVE_NEW_DATA | true | อนุญาต auto-approve เฉพาะ แถวใหม่ และเฉพาะไฟล์ที่ไม่ใช่ human upload |
CORETH_AUTO_APPROVE_ADDITIVE_SCHEMA | true | อนุมัติอัตโนมัติได้เฉพาะการเพิ่ม field ที่ไม่บังคับ |
CORETH_REVIEW_PAUSE_APPLY | false | สวิตช์หยุดทั้งหมด — run_review_jobs() return ทันที ไม่มีการเผยแพร่ข้อมูลหรือ schema |
CORETH_AI_ENRICHMENT_ENABLED | true | ให้ AI เติมค่าลงแถว app_output ที่มีอยู่จาก PDF/ข้อความ (fill-only) |
CORETH_COMPACT_RECORDS | true | เก็บรายละเอียดลงไฟล์ใน record-artifacts แทนการสะสมแถวในฐานข้อมูล |
CORETH_OCR_ALLOWED | false | เปิด TesseractOcrEngine (image มี tesseract tha+eng ติดตั้งไว้แล้ว) |
5.4 ค่าที่เป็น secret reference (ห้ามใส่ค่าจริง)
| ตัวแปร | ต้องมี key อะไรใน secret | อ่านโดย |
|---|---|---|
CORETH_AI_SECRET_REF | OPENAI_API_KEY | semantic/credentials.py |
OPENAI_API_KEY_REF | OPENAI_API_KEY | runtime_secrets.py ตอน entrypoint |
CONTEXT_CHANGED_WEBHOOK_SECRET_REF | bearer_token | runtime.build_dispatcher() |
CORETH_BACKUP_KEY_SECRET_REF | คีย์เข้ารหัสสำรองข้อมูล | operations/backup.py |
CORETH_OPENWEBUI_ARCHIVE_SECRET_REF | issuer, audience, subject, kid, private_key | openwebui_archive.py → FileIngestionClient |
secret ของ source (secret_ref ใน source config) | ขึ้นกับ connector — เช่น FILE_READY ต้องมี file_ingestion.secret_ref พร้อม JWT key | connectors/factory.py |
OPENAI_API_KEY (ค่าจริง) พร้อมกับ OPENAI_API_KEY_REF ไม่ได้ —
entrypoint จะจบด้วย exit 78 และ container ไม่ขึ้น
แนะนำให้ใช้ _REF เสมอในสภาพแวดล้อมจริง
5.5 ค่าที่ wizard สร้างหรือจัดการให้
| ตัวแปร | ที่มา |
|---|---|
DJANGO_SECRET_KEY, POSTGRES_PASSWORD | สุ่มใหม่เมื่อยังว่างหรือขึ้นต้นด้วย replace-with- |
COMPOSE_PROFILES | คำนวณจากคำตอบเรื่องฐานข้อมูลและ Monitor |
CONTEXT_DATABASE_URL, DATA_DATABASE_URL | ถามแบบซ่อนข้อความ (getpass) และทดสอบต่อจริงก่อนบันทึก |
CORETH_PUBLIC_HOST, CLOUDFLARE_TUNNEL_TOKEN | tunnel_preflight กู้จาก container ที่รันอยู่ หรือถามใหม่ |
CORETH_AI_PROVIDER, CORETH_AI_SECRET_REF, CORETH_AI_MODEL | เลือกจากรายการโมเดลจริงของ provider |
5.6 ไม่มีผลเมื่อรันด้วย Docker Compose
ตัวแปรกลุ่มนี้อยู่ใน .env.advanced.example และถูกอ่านใน config/settings.py
แต่ ไม่ได้อยู่ในบล็อก &app-env ของ compose.yaml
จึงไปไม่ถึง container การแก้ใน .env จึงไม่มีผลใด ๆ
| ตัวแปร | สาเหตุ | ถ้าต้องเปลี่ยนจริงต้องทำอย่างไร |
|---|---|---|
DJANGO_DEBUG | compose ตรึงเป็น "false" | แก้ compose.yaml หรือ override file |
DATABASE_URL | compose ประกอบเองจาก POSTGRES_PASSWORD | ใช้ CONTEXT_DATABASE_URL แทน |
CORETH_BACKUP_DIRECTORY | compose ตรึงเป็น /var/lib/coreth/backups (ผูกกับ volume) | เปลี่ยน mount ของ volume backup-data |
CORETH_LOG_LEVEL | ไม่ได้ส่งต่อ | เพิ่มบรรทัดใน &app-env ถ้าต้องการ |
DATABASE_CONN_MAX_AGE | ไม่ได้ส่งต่อ · ค่าจริงคือ 0 | เพิ่มใน &app-env |
CORETH_APPLICATION_VERSION, CORETH_SCHEMA_VERSION, CORETH_TAXONOMY_VERSION, CORETH_PARSER_VERSION, CORETH_PROVIDER_CONTRACT_VERSION | ไม่ได้ส่งต่อ · ใช้ค่า default ใน settings.py เสมอ | ปกติไม่ควรเปลี่ยน — เป็นค่าที่ Doctor รายงาน |
DJANGO_SETTINGS_MODULE | image ตั้งไว้แล้ว | — |
ค่าปรับแต่งอื่น ๆ ที่ส่งถึง container จริง (ดู .env.advanced.example)
| กลุ่ม | ตัวแปร | ค่าเริ่มต้น |
|---|---|---|
| ขีดจำกัดตัวแยกไฟล์ | CORETH_PARSER_MAX_BYTES · _MAX_PAGES · _MAX_ROWS · _MAX_ARCHIVE_ENTRIES · _MAX_UNCOMPRESSED_BYTES · _MAX_COMPRESSION_RATIO · _MAX_PROCESSING_SECONDS | 25 MB · 300 · 100000 · 10000 · 200 MB · 100 · 60 s |
| รอบ worker | WORKER_POLL_SECONDS · CORETH_SYNC_BATCH_SIZE · CORETH_RECONCILIATION_INTERVAL_SECONDS | 2 · 100 · 3600 |
| Readiness | CORETH_DOCTOR_INTERVAL_SECONDS · CORETH_DOCTOR_MAX_AGE_SECONDS · CORETH_WORKER_HEARTBEAT_MAX_AGE_SECONDS | 60 · 300 · 120 |
| Provider (Service JWT) | CORETH_SERVICE_JWT_ISSUER · _AUDIENCE · _PUBLIC_KEYS_JSON · _MAX_TTL_SECONDS · _CLOCK_SKEW_SECONDS | — · — · {} · 300 · 30 |
| Context Slice | CORETH_SLICE_DEFAULT_LIMIT · _MAX_ENTITIES · _MAX_FACTS · _MAX_RELATIONSHIPS · _MAX_EVIDENCE_REFS | 50 · 100 · 500 · 250 · 500 |
| Data Provider API | CORETH_DATA_DEFAULT_PAGE_SIZE · _MAX_PAGE_SIZE · _EVENT_RETENTION_DAYS | 100 · 1000 · 30 |
| Snapshot | CORETH_SNAPSHOT_BUCKET · _PREFIX · _KMS_KEY_ID · _MAX_BYTES · _RETENTION_DAYS | ว่าง · coreth-input-snapshots · ว่าง · 512 MB · 30 |
| สำรองข้อมูล | CORETH_BACKUP_INTERVAL_SECONDS · _RETENTION_DAYS · _MAX_AGE_SECONDS · CORETH_RESTORE_AFTER_BACKUP · CORETH_RESTORE_REHEARSAL_MAX_AGE_SECONDS | 86400 · 30 · 129600 · true · 604800 |
| Monitor logs | CORETH_ADMIN_LOG_GROUP · _STREAM_PREFIX · _LIMIT · CORETH_ADMIN_PORT | ว่าง · ว่าง · 100 (สูงสุด 500) · 8081 |
6. Source / Connector Setup#
Source configuration เป็น immutable — ทุกการแก้ไขสร้าง config_version ใหม่
และ latest_source_configurations() จะเลือกเฉพาะเวอร์ชันสูงสุดมาทำงาน
ประวัติเดิมยังอยู่ครบเพื่อสืบย้อน lineage
6.1 File Ingestion (FILE_READY) IMPLEMENTED
ช่องทางหลักของไฟล์ที่เข้ามาอัตโนมัติ (Gmail, LINE, Web, API upload ฯลฯ) Context Processor ไม่ได้ต่อเข้ากล่องอีเมลเอง — มันคุยกับ File Ingestion service เท่านั้น
สิ่งที่ต้องเตรียมก่อน
- HTTPS base URL ของ File Ingestion — ต้องพิมพ์เอง (เป็นบริการภายนอก ระบบค้นหาให้ไม่ได้)
- Secret ของ service reader ใน Secrets Manager ที่มี key:
issuer,audience,subject,kid,private_keyและ (ไม่บังคับ)algorithmค่าเริ่มต้นRS256 - สิทธิ์
file:readและevents:readฝั่ง File Ingestion
ตั้งค่าผ่าน Admin (แนะนำ)
- เปิด
/admin/setupด้วยบัญชีบทบาทADMIN - เลือกประเภท File Ingestion แล้วกรอก base URL + secret reference
- กดค้นหา — ระบบเรียก inventory จริงและ แสดงรายชื่อแหล่งข้อมูลที่พบให้เลือก
(
ingestion_source_options()แปลงsource_typeเป็นชื่อที่อ่านออก เช่น “Gmail — ฝ่ายขาย”) พร้อมเดา bucket ให้จาก inventory - เลือกโหมด schema: AI จัดการอัตโนมัติ หรือ กำหนด fields เอง
- เลือกโมเดลจากปุ่มโหลดรายการ (
/admin/setup/models) แล้วบันทึก
ตั้งค่าผ่าน CLI (สำหรับ automation)
docker compose exec api python manage.py configure_source \
--source-type FILE_READY \
--settings-file /app/examples/file-ingestion/thinkberry.json
examples/file-ingestion/ ยังตั้ง
"semantic_processing": {"mode": "DETERMINISTIC_ONLY"} ซึ่ง
ใช้ไม่ได้แล้ว เมื่อ CORETH_AGENTIC_ONLY=true — ต้องแก้เป็น
"mode": "AI_REQUIRED" พร้อม allow_external_model และ
secret_ref ก่อนใช้จริง
พฤติกรรมเมื่อไม่ส่ง flag ครบ
ตรวจว่าเชื่อมต่อได้จริง
sources: [] = ไม่มี source ที่ policy ผิดพลาด ปิดกั้นไฟล์ดิบ หรือมี last_error_code ค้าง
นำไฟล์เดิมทั้งหมดเข้าครั้งแรก
onboard_file_ingestion ดึง inventory ทั้งหมด ตาม event ให้ทัน ประมวลผล
และ project จนหมด — รันซ้ำได้ ไม่ทำงานซ้ำซ้อน เพราะมี FileIngestionCursor
ONBOARDING_FAILED ให้ดู SourceSignal.error_code ที่หน้า
/admin/ingestion-history · ถ้าได้ ONBOARDING_INCOMPLETE แปลว่าครบ
--max-cycles (ค่าเริ่มต้น 1000) ให้รันซ้ำเพื่อทำต่อจากเดิม
6.2 OpenWebUI direct upload (OPENWEBUI_UPLOAD) IMPLEMENTED
ผู้ใช้อัปโหลดไฟล์จาก OpenWebUI เข้ามาโดยตรงผ่าน POST /api/v1/submissions
ด้วย Service JWT แล้วตรวจทานผลผ่าน /api/v1/openwebui/submissions
ทุกไฟล์กลุ่มนี้ต้องมีคนกดยืนยันเสมอ ไม่มีเส้นทาง auto-approve
| # | สิ่งที่ต้องทำ | เครื่องมือ |
|---|---|---|
| 1 | มีศูนย์ (Center) และ source ต้นแบบที่ผูกกับศูนย์นั้นอยู่แล้ว | /admin/centers หรือ configure_review_scope |
| 2 | สร้าง source แบบ push โดยคัดลอกการตั้งค่าประมวลผลจาก source ต้นแบบ | configure_openwebui_source |
| 3 | ผูก OpenWebUI user id เข้ากับศูนย์ (ไม่สร้าง login ในระบบนี้) | configure_openwebui_reviewer (ถูกเรียกอัตโนมัติจากขั้นที่ 2) |
| 4 | ออก Service grant ให้ tool | manage_tool_grant configure |
| 5 | ตั้ง public key ของผู้ออก JWT | CORETH_SERVICE_JWT_PUBLIC_KEYS_JSON |
สร้าง source และผูกผู้ใช้ — เลือกจากรายการจริง
คำสั่งนี้ถามสามอย่าง: source ต้นแบบ, ศูนย์ และ OpenWebUI user id สองอย่างแรกมาเป็นเมนูตัวเลขจากฐานข้อมูล ส่วน user id ต้องพิมพ์เองเพราะเป็นตัวตนของระบบภายนอก
บทสนทนาจริง
นำ JSON ที่ได้ไปตั้งเป็น user map ฝั่ง OpenWebUI เพื่อให้ออก JWT ที่มี claim ตรงกัน
ออก Service grant
integrations/openwebui/submit_filter.py (ติดตั้งเป็น global Filter)
มันอ่าน credential และ user map จากไฟล์/ตัวแปรที่ผู้ดูแลตั้งไว้เท่านั้น
ไม่รับค่าจากข้อความแชทหรือ argument ของโมเดล
allowed_scopes (เช่น submissions:read,
submissions:write) และ allowed_actions ทุก request
ต้องมี scope ครบทั้งใน token และใน grant มิฉะนั้นได้ SUBMISSION_GRANT_REQUIRED
Service JWT ที่ระบบยอมรับ
| ข้อกำหนด | ค่า |
|---|---|
| Algorithm | EdDSA, RS256 หรือ ES256 เท่านั้น และต้องมี kid |
| Claim ที่บังคับ | iss, aud, sub, customer_id, tool_type, tool_instance_id, scopes, action, iat, exp, jti |
| Issuer / Audience | ต้องตรงกับ CORETH_SERVICE_JWT_ISSUER / _AUDIENCE |
| อายุสูงสุด | CORETH_SERVICE_JWT_MAX_TTL_SECONDS (300 วินาที) |
| Public key | อยู่ใน CORETH_SERVICE_JWT_PUBLIC_KEYS_JSON เป็น map {"<kid>": "<PEM>"} |
ทดสอบหนึ่งไฟล์แบบครบวงจร
- อัปโหลดไฟล์จาก OpenWebUI (ระบบสร้าง
Submission+OpenWebUIUploadReceipt) - รอ worker รัน
ReviewJob(ANALYZE)— ดูสถานะที่/admin/submissions - ตรวจแถวที่เสนอ แก้ค่าที่ผิด แล้วกดยืนยัน
- ตรวจข้อมูลที่เผยแพร่แล้วที่
/admin/data-explorer
ส่งไฟล์ดิบของ OpenWebUI กลับไปเก็บที่ File Ingestion (ไม่บังคับ)
เมื่อผู้ใช้อัปโหลดไฟล์ตรงจาก OpenWebUI ไฟล์ดิบจะอยู่ในตัว Context Processor ฟีเจอร์นี้ส่งไฟล์นั้นกลับไปเก็บที่ File Ingestion เพื่อให้ไฟล์ทุกใบ ไม่ว่ามาทางไหน อยู่ภายใต้ lifecycle และ trust state เดียวกัน
ขั้นที่ 1 · ให้สิทธิ์ฝั่ง File Ingestion
การอัปโหลดใช้ action file:archive-openwebui ซึ่งต้องถูกอนุญาตไว้ที่
File Ingestion ก่อน มิฉะนั้นทุกครั้งจะได้ ARCHIVE_HTTP_403
คำสั่งนี้ รันบนเครื่อง File Ingestion ไม่ใช่เครื่อง Context Processor
docker exec -it coreth-file-ingestion-api-1 \
python manage.py configure_openwebui_archive \
--subject service:context-processor
--subject ต้อง ตรงกับฟิลด์ subject ใน secret
ที่ CORETH_OPENWEBUI_ARCHIVE_SECRET_REF ชี้อยู่ เพราะ
archive_receipt() ใส่ค่านั้นเป็น claim sub ของ JWT ทุกใบ
ถ้าสองค่าไม่ตรงกัน File Ingestion จะปฏิเสธแม้จะให้สิทธิ์ไว้แล้ว
subject เดียวกันนี้ต้องมีสิทธิ์ สองชุด เพราะการ archive หนึ่งครั้งเรียก File Ingestion สองขา:
| ขา | Endpoint | Action ใน JWT | ใช้ทำอะไร |
|---|---|---|---|
| อัปโหลด | POST /v1/openwebui/archives |
file:archive-openwebui |
ส่งไฟล์ดิบ (multipart) · ต้องได้ HTTP 202 และ JSON ที่มี file_version_id |
| ตรวจซ้ำ | GET /v1/file-versions/{id} |
file:read, events:read |
ยืนยันว่าไฟล์ถูกรับจริง · ตรวจ provenance และ sha256 ก่อนปิดเป็น COMPLETED |
file:archive-openwebui แต่ลืม file:read
ไฟล์จะอัปโหลดขึ้นไปได้ แต่ขาตรวจซ้ำจะล้มเสมอ ทำให้ receipt ค้างอยู่ที่
PROCESSING / RETRY วนไม่จบ
ขั้นที่ 2 · เตรียม secret
secret ต้องเป็น JSON object ที่มีคีย์เหล่านี้ (โครงเดียวกับ service reader ของ FILE_READY)
และ subject ต้องตรงกับที่ให้สิทธิ์ไว้ในขั้นที่ 1
algorithm ไม่บังคับ ค่าเริ่มต้นคือ RS256 ·
token มีอายุ 120 วินาที และมี jti ใหม่ทุกใบ
ขั้นที่ 3 · เปิดใช้งานฝั่ง Context Processor
ตั้งสองค่าพร้อมกันเท่านั้นจึงเปิดใช้งาน ถ้าเว้นตัวใดตัวหนึ่งว่าง ฟีเจอร์นี้จะปิดเงียบ ๆ
URL ต้องเป็น https ล้วน ไม่มี path, query, fragment หรือ credential
(ARCHIVE_INVALID_URL) การโอนย้ายทำโดย worker รอบปกติผ่าน
reconcile_openwebui_archives() ซึ่งถูกเรียกท้าย
run_review_jobs() ทุกรอบ ไม่ต้องตั้ง cron แยก
ขั้นที่ 4 · ตรวจผล
สถานะอยู่ที่ฟิลด์ archive_state ของ OpenWebUIUploadReceipt
เฉพาะ submission ที่ COMPLETED แล้วเท่านั้นที่เข้าคิว archive
archive_state | ความหมาย |
|---|---|
PENDING | รอส่งครั้งแรก |
PROCESSING | File Ingestion รับไฟล์แล้ว (ได้ 202) แต่ยังไม่ consumable · ตรวจซ้ำทุก 60 วินาที |
RETRY | ล้มชั่วคราว · backoff 30 × 2^attempts สูงสุด 3600 วินาที |
COMPLETED | ไฟล์ consumable และ sha256 ตรงกับตอนอัปโหลด |
BLOCKED | ไฟล์ถูกปฏิเสธ/กักกัน/ปิด lifecycle ที่ปลายทาง · ต้องสั่งลองใหม่เอง |
ปกติไม่ต้องสั่งอะไร worker จะลองใหม่ให้เองตาม backoff
สองคำสั่งข้างล่างใช้เมื่อต้องการเร่งให้เกิดขึ้นเดี๋ยวนี้
ทั้งคู่รันบนเครื่อง Context Processor และอ่าน customer UUID
จาก .env ผ่าน environment ของคอนเทนเนอร์ จึงไม่ต้องพิมพ์ UUID เอง
docker exec coreth-context-processor-worker-1 python manage.py shell -c '
import os
from coreth_data.models import OpenWebUIUploadReceipt
from coreth_data.openwebui_archive import reconcile_openwebui_archives
customer = os.environ["CORETH_DEPLOYMENT_CUSTOMER_ID"] # มาจาก .env ผ่าน compose
assert customer, "CORETH_DEPLOYMENT_CUSTOMER_ID ว่างในคอนเทนเนอร์"
pending = OpenWebUIUploadReceipt.all_objects.filter(
customer_id=customer,
submission__status="COMPLETED",
revoked_at__isnull=True,
archive_state__in=["PENDING", "RETRY"],
)
print("Queued:", pending.update(archive_next_attempt_at=None))
reconcile_openwebui_archives(limit=20)
'
ผลที่ต้องเห็น
archive_next_attempt_at=None คือการล้างนาฬิกา backoff ให้รายการที่ค้างอยู่
ถูกหยิบทันทีในรอบนี้ ไม่ได้ข้ามเงื่อนไขอื่น — receipt ที่ถูก revoke
หรือ submission ที่ยังไม่ COMPLETED ยังไม่ถูกแตะ ·
ตัวกรองนี้ครอบทุก environment ของ customer นั้น
ถ้าใช้ฐานเดียวร่วมกับ staging ให้เพิ่ม environment="production" ใน filter
# BLOCKED ไม่ถูกหยิบเองอีก ต้องรีเซ็ตกลับเป็น PENDING ก่อน
# sh -c + single quote: ตัวแปรถูกขยายในคอนเทนเนอร์ ไม่ใช่บน host
docker exec coreth-context-processor-worker-1 sh -c 'python manage.py reconcile_openwebui_archives \
--retry-blocked --customer-id "$CORETH_DEPLOYMENT_CUSTOMER_ID"'
สิ่งที่ Context Processor ส่งไปและตรวจกลับ
ขาอัปโหลดส่ง form fields: instance_id, user_id,
email, submission_id, processor_source_id,
environment พร้อมไฟล์ในฟิลด์ file
ขาตรวจซ้ำบังคับว่า source_provenance ของ file version ต้องมีค่าตรงกันทุกช่อง
มิฉะนั้นได้ ARCHIVE_REFERENCE_MISMATCH
ฟิลด์ใน source_provenance | ต้องเท่ากับ |
|---|---|
processor_submission_id | Submission ที่เป็นต้นทาง |
processor_source_id | source ของ submission นั้น |
openwebui_user_id | ส่วนท้ายของ actor_id หลัง openwebui:<instance>: |
openwebui_instance_id | receipt.instance_id |
processor_environment | receipt.environment |
archive_sha256 | checksum ของไฟล์ตอนอัปโหลดเข้า Processor |
6.3 Connector อื่น ๆ ที่มีโค้ดแต่ไม่มีใน Admin UI
S3, GOOGLE_DRIVE, POSTGRESQL, MYSQL,
MARIADB, REST มี adapter ครบใน coreth_context/connectors/
และตั้งค่าได้ด้วย manage.py configure_source --source-type …
แต่หน้า /admin/setup เปิดให้เลือกแค่ FILE_READY กับ OPENWEBUI_UPLOAD
semantic_processing.mode = AI_REQUIRED ใน source config
มิฉะนั้น CORETH_AGENTIC_ONLY=true จะทำให้ทุก signal fail ด้วย
AGENTIC_REQUIRED ตรวจได้ด้วย list_source_policies
6.4 การหยุด / เปิดใหม่ / เก็บเข้าคลัง
| ต้องการ | ทำที่ | ผล |
|---|---|---|
| หยุด source ชั่วคราว | POST /admin/setup/<source_id>/pause | สร้าง SourcePause · งานที่เริ่มไปแล้วทำต่อจนจบ · signal ใหม่และ reconcile ถูกข้ามจนกว่าจะ resume |
| เปิดใหม่ | POST /admin/setup/<source_id>/resume | ลบ pause |
| เก็บเข้าคลัง (เลิกใช้ถาวร) | POST /admin/setup/archive | สร้าง ArchivedSource โดยไม่แก้ source config เดิม · signal ใหม่ถูกข้าม |
| ปลดล็อกหลังเครดิต AI หมด | manage.py resume_ai | ไม่ใส่ flag = แสดงรายการ hold ทั้งหมดเป็น JSON · ใส่ --source-id + --customer-id เพื่อปลดล็อก |
| หยุดงานที่ค้างกลางคัน | POST /admin/source-signals/<id>/force-stop | เขียนไฟล์ stop ใน record-artifacts/signal-control/ และฆ่า child process ของ signal นั้น (ต้องมีบทบาท OPERATOR ขึ้นไป) |
7. Validation / Doctor / Health#
ระบบมีกลไกตรวจความพร้อมของตัวเองอยู่แล้ว ใช้มันแทนการเดา
ContextDoctor ประเมินจากข้อเท็จจริงที่บันทึกไว้ในฐานข้อมูลเท่านั้น
ผลลัพธ์จึงทำซ้ำได้ (deterministic)
7.1 ระดับความพร้อม
| Endpoint / คำสั่ง | บอกอะไร | ค่าที่ถือว่าผ่าน |
|---|---|---|
GET /health/live | process ยังอยู่ (liveness ของ container) | HTTP 200 · "phase":"FOUNDATION" · ready เป็น false เสมอ |
GET /health/ready | readiness ที่ Doctor บันทึกไว้ล่าสุด | HTTP 200 · "ready":true · "phase":"READY" |
manage.py context_status | สรุป source + Initial Build + อายุผล Doctor | "ready": true |
manage.py context_doctor | รันตรวจทุกข้อและบันทึกผลใหม่ | "status":"PASS" |
manage.py structured_data_status | สถานะ dataset และเวอร์ชันล่าสุดที่ปลอดภัย | มี latest_ready_version ทุก dataset ที่ใช้งาน |
/admin/server-status | ปุ่มทดสอบ Context DB / Data DB / AI / File Ingestion แบบสด | ขึ้น “SELECT 1 สำเร็จ” |
7.2 ลำดับเฟสของ readiness
coreth_context/operations/doctor.py::_phase() และ coreth_context/views.py::deployment_readiness()7.3 รัน Doctor
ไม่ต้องใส่ --customer-id เพราะอ่านจาก CORETH_DEPLOYMENT_CUSTOMER_ID ให้เอง
ผลที่ต้องเห็น (ตัดมาบางส่วน)
สรุปเฉพาะข้อที่ไม่ผ่าน:
docker compose exec -T api python manage.py context_doctor | python3 -c '
import json,sys
r=json.load(sys.stdin)
for c in r["checks"]:
print("{:5} {:26} {}".format(c["status"], c["code"], c["message"]))
print()
print("PHASE:", r["phase"], "| STATUS:", r["status"])
print("REASONS:", ", ".join(r["reason_codes"]) or "-")'
7.4 รายการตรวจทั้งหมดของ Doctor
| Code | ตรวจอะไร | กระทบ phase? |
|---|---|---|
POSTGRESQL | ต่อฐาน canonical ได้ | — |
MIGRATIONS | ไม่มี migration ค้าง | FAILED |
DATA_DATABASE | ฐาน data ต่อได้และ migrate แล้ว | — |
SPLIT_DATABASE_RECOVERY | backup/restore ครอบคลุมทั้งสองฐานเมื่อแยกฐาน | — |
SOURCES_CONFIGURED | มี source ที่อนุมัติแล้วอย่างน้อยหนึ่ง | INITIALIZING |
SOURCE_SCOPES | ทุก source มี approved scope ชัดเจน | — |
CREDENTIAL_REFERENCES | secret_ref เป็น reference ไม่ใช่ค่าจริง | — |
SOURCE_HEALTH | สุขภาพ source ตามระดับ criticality | CONNECTING |
SOURCE_VERIFICATION | credential + scope ถูกยืนยันภายในอายุที่กำหนด | — |
SOURCE_CHECKPOINTS | มี checkpoint ทั้ง INITIAL_SCAN และ RECONCILIATION | — |
INITIAL_BUILD | Initial Context Build เคยรันและสถานะล่าสุด | SCANNING |
CANONICAL_CONTEXT | มี Context + ContextVersion จริง | STRUCTURING |
CONTINUOUS_SYNC | sync ล่าสุดไม่เก่าเกิน CORETH_SYNC_MAX_AGE_SECONDS | — |
RECONCILIATION | reconcile ล่าสุดไม่เก่าเกินกำหนด | RECONCILING |
WORKER_HEARTBEAT | ServiceHeartbeat(worker) ใหม่กว่า 120 วินาที | — |
EXTRACTION_FAILURES | สถานะสกัดล่าสุดยอมรับได้ | — |
CONTEXT_QUALITY | ความขัดแย้ง/ความสดของ Fact | — |
CONTEXT_PROVIDER | public key + grant ของ provider ใช้ได้ | — |
CONTEXT_CHANGED_DELIVERY | outbox ไม่ค้างเกิน CORETH_EVENT_PENDING_MAX_AGE_SECONDS | — |
DATA_OUTBOX_PROJECTION | ความล่าช้าของ projection จาก data → context | — |
STRUCTURED_DATA | Dataset readiness และเวอร์ชันที่ปลอดภัยล่าสุด | — |
TRANSFORM_RUNS | TransformRun ที่ล้มหรือถูกกักไว้ | — |
AI_POLICY | policy ของ semantic processing ใช้งานได้ | — |
BACKUP_HEALTH | มี backup ใหม่กว่า CORETH_BACKUP_MAX_AGE_SECONDS | — |
RESTORE_REHEARSAL | ซ้อมกู้คืนใหม่กว่า CORETH_RESTORE_REHEARSAL_MAX_AGE_SECONDS | — |
CONTROL_PLANE | EffectivePolicy + WorkAdmission มองเห็นได้ | — |
V11_RECONCILIATION | lineage ข้ามโดเมนสอดคล้องกัน | — |
ข้อที่ไม่ได้ทำให้เปลี่ยน phase โดยตรง ยังทำให้ phase เป็น BLOCKED (ถ้ามี FAIL)
หรือ DEGRADED (ถ้ามี WARN) ได้ตามกฎรวมท้ายสุด
7.5 Smoke test ครบวงจรหนึ่งรอบ
ทดสอบเส้นทางจริงตั้งแต่ไฟล์เข้าจนถึงข้อมูลที่เผยแพร่ ใช้เวลาไม่กี่นาที
# 0) ค่าพื้นฐานจาก .env
set -a; . ./.env; set +a
PORT="${CORETH_HTTP_PORT:-8080}"
# 1) liveness ผ่าน Caddy
curl -fsS "http://127.0.0.1:$PORT/health/live" >/dev/null \
&& echo "PASS api liveness" || echo "FAIL api liveness"
# 2) migration ครบ (ไม่มีบรรทัดที่ยังไม่ติ๊ก)
docker compose exec -T api python manage.py migrate --check >/dev/null 2>&1 \
&& echo "PASS migrations" || echo "FAIL migrations (รัน docker compose run --rm migrate)"
# 3) worker เต้นอยู่จริง — บังคับหนึ่งรอบแล้วดูผลนับ
docker compose exec -T api python manage.py runworker --once
# 4) Doctor + readiness
docker compose exec -T api python manage.py context_doctor >/dev/null
curl -sS -o /tmp/ready.json -w 'HTTP %{http_code}\n' "http://127.0.0.1:$PORT/health/ready"
cat /tmp/ready.json; echo
# 5) dataset ที่เผยแพร่แล้ว
docker compose exec -T api python manage.py structured_data_status
# 6) งานตรวจทานที่ยังค้าง (อ่านอย่างเดียว ไม่เปลี่ยนสถานะ)
docker compose exec -T api python manage.py review_new_data
ผลที่ต้องเห็น
/health/ready ตอบ 503 ให้ดูค่า reason ก่อนเสมอ —
CUSTOMER_NOT_CONFIGURED = .env ไม่มี UUID ที่ถูกต้อง ·
DOCTOR_NOT_EVALUATED = worker ยังไม่เคยรัน ·
DOCTOR_STALE = worker หยุดไปนานกว่า 300 วินาที ·
ค่าอื่นคือ reason_codes[0] จาก Doctor
ตรวจว่า RLS ถูกเปิดจริงบน PostgreSQL
secure_database เปิด RLS ให้ทุกตารางที่ Django จัดการ (รวม django_migrations)
และถอนสิทธิ์ PUBLIC, anon, authenticated
— สำคัญมากเมื่อฐานข้อมูลอยู่บน Supabase ที่เปิด REST API ให้ role เหล่านี้โดยปริยาย
8. Operations#
งานประจำหลังติดตั้ง ทุกคำสั่งมีอยู่จริงใน repository
8.1 Deploy โค้ดใหม่โดยไม่ตัด ingestion
worker อยู่ใน profile แยกโดยเจตนา จึงอัปเดตเว็บได้โดยไม่แตะงานที่กำลังทำ
แล้วค่อยรีสตาร์ต worker ตอนที่ว่าง
git pull
docker compose config --quiet # ตรวจไวยากรณ์ก่อนเสมอ
docker compose up -d --build --no-deps --wait --wait-timeout 300 api admin caddy
docker compose exec -T api python manage.py review_new_data # ดูว่ามีอะไรค้างไหม
docker compose up -d --build --no-deps worker
docker compose logs --tail=10 worker
docker compose up -d เปล่า ๆ พร้อม COMPOSE_PROFILES
ที่มี worker หรือ * — จะ recreate worker และตัดงานกลางคัน
ถ้าต้องหยุดงานที่ค้างจริง ๆ ให้ใช้ปุ่ม force stop ที่ /admin/ingestion-history
ซึ่งฆ่าเฉพาะ child process ของ signal นั้นและบันทึก FORCE_STOPPED_BY_OPERATOR
8.2 Upgrade ที่มี migration
scripts/upgrade.sh ห่อลำดับทั้งหมดไว้แล้ว: สำรอง → ซ้อมกู้คืน → Doctor →
preflight → migrate → check → Doctor อีกครั้ง ทุกขั้นหยุดทันทีเมื่อผิดพลาด (set -eu)
สำหรับการย้อนกลับ (rollback) ต้องอ้างเอกสาร ADR ที่อนุมัติแล้ว:
sh -c '…' ครอบด้วย single quote โดยตั้งใจ เพื่อให้
$CORETH_DEPLOYMENT_CUSTOMER_ID ถูกขยายในคอนเทนเนอร์
ไม่ใช่บน host shell ซึ่งตัวแปรจะว่างถ้ายังไม่ได้ source .envupgrade_preflight ปฏิเสธถ้า --customer-id ไม่ตรงกับ
CORETH_DEPLOYMENT_CUSTOMER_ID และตรวจว่ามี backup + restore rehearsal
ที่ยังไม่หมดอายุจริง จึงข้ามขั้นตอนสำรองข้อมูลไม่ได้
8.3 สำรองและกู้คืน
| งาน | คำสั่ง | ผลลัพธ์ |
|---|---|---|
| สำรองทันที | manage.py backup_context | ไฟล์ CORETHBK1 (AES-GCM) + BackupArtifact พร้อม checksum |
| ซ้อมกู้คืน | manage.py restore_rehearsal | กู้เข้าฐานแยกต่างหากแล้วตรวจ · RestoreRehearsal |
| สำรองตามรอบอัตโนมัติ | docker compose --profile ops up -d backup | runbackup_scheduler ทำงานทุก CORETH_BACKUP_INTERVAL_SECONDS |
| ตรวจสถานะหลังกู้คืนจริง | manage.py reconcile_restored_state | gate ตรวจอย่างเดียว ไม่แก้ข้อมูล |
CORETH_BACKUP_KEY_FILE หรือ CORETH_BACKUP_KEY_SECRET_REF
อย่างใดอย่างหนึ่งก่อน มิฉะนั้น backup_context จะ error
และ Doctor จะรายงาน BACKUP_HEALTH เป็น WARN ตลอด
เก็บคีย์ไว้คนละที่กับไฟล์สำรอง
8.4 คำสั่งสำหรับงานประจำ
| เมื่อไหร่ | คำสั่ง | ทำอะไร |
|---|---|---|
| ต้องการดูสถานะรวม | context_status | source + Initial Build + readiness + sync metrics |
| ต้องการดู dataset | structured_data_status | เวอร์ชันล่าสุด/ล่าสุดที่ปลอดภัยของทุก dataset |
| ตรวจนโยบาย AI ของทุก source | list_source_policies --issues-only | เฉพาะ source ที่ policy ผิดหรือบล็อกไฟล์ดิบ |
| เครดิต AI หมด | resume_ai แล้ว resume_ai --source-id … --customer-id … | ดูรายการ hold แล้วปลดล็อกทีละ source |
| Transform ค้างกลางคัน | recover_interrupted_transforms | กู้โดยไม่ลบข้อมูลหรือประวัติที่ commit แล้ว |
| ต้องประมวลผลไฟล์เดิมใหม่ | reprocess_structured_data | สร้าง TransformRun/DatasetVersion ใหม่แบบ immutable |
| Snapshot กินพื้นที่ | purge_input_snapshots | ลบเฉพาะ snapshot ที่ CP เป็นเจ้าของและหมดอายุ · ไม่แตะไฟล์ของ File Ingestion |
| ยอดรวมไฟล์ไม่ตรง | reconcile_file_collections | ดู/คำนวณยอดใหม่ต่อ customer + environment |
| ต้นทางเปลี่ยน Source UUID | remap_file_ingestion_source | ดู/ซ่อม mapping โดยคงประวัติเดิม |
| ส่ง Context Changed ค้าง | dispatch_context_events | ส่ง outbox ที่ถึงกำหนดแบบ at-least-once |
| รัน review job ทันที | run_review_jobs | รันหนึ่งรอบ · ลองใหม่เฉพาะ job ที่ล้มได้ |
คำสั่งข้างต้นเรียกด้วยรูปแบบเดียวกัน:
docker compose exec api python manage.py <command>
(ใส่ -T เมื่อไม่ต้องการให้ถามโต้ตอบ เช่นในสคริปต์ CI)
8.5 ดู log
docker compose logs -f worker # JSON บรรทัดละ event
docker compose logs --tail=200 api | grep -i error
docker compose logs --tail=50 cloudflared # เมื่อใช้ overlay
Log เป็น JSON เสมอ (coreth_context/logging.py::JsonLogFormatter)
ฟิลด์ที่มีประโยชน์: event, customer_id, source_id,
error_code, status · ระหว่างประมวลผลหนึ่ง signal จะเห็น
signal_progress พร้อม phase
(FETCH_METADATA → DOWNLOAD_CONTENT →
FILE_AGENT_ANALYSIS หรือ EXTRACT_DOCUMENT) และเวลาที่ใช้
CORETH_ADMIN_LOG_GROUP หน้า /admin/activity
จะดึง log จาก CloudWatch มาแสดง โดยกรองเฉพาะ event ของ customer นี้
และแสดงเฉพาะฟิลด์ที่อยู่ใน allowlist ไม่แสดงข้อความดิบ
8.6 รันชุดทดสอบ
มีเทสต์ Python 64 ไฟล์ใน tests/ และเทสต์ UI ด้วย node --test
(npm run test:explorer, npm run test:schema-design)
9. Troubleshooting#
อาการที่เกิดขึ้นได้จริงจากเส้นทาง error ที่มีในโค้ด เรียงตามสิ่งที่พบบ่อยตอนติดตั้ง
9.1 ติดตั้งและเริ่มระบบ
| อาการ | ตรวจ | แก้ |
|---|---|---|
api ขึ้นแล้วดับทันที exit 78 |
docker compose logs api เห็น runtime secret injection failed |
OPENAI_API_KEY_REF resolve ไม่ได้ หรือถูกตั้งพร้อม OPENAI_API_KEY · ตรวจ IAM, AWS_REGION และให้ secret มี key OPENAI_API_KEY |
docker compose config error ว่า CORETH_PUBLIC_HOST ไม่ถูกตั้ง |
grep COMPOSE_FILE .env |
overlay Cloudflare ถูกเปิดแต่ยังไม่มีค่า · เติม CORETH_PUBLIC_HOST + CLOUDFLARE_TUNNEL_TOKEN หรือเอา compose.cloudflare.yaml ออกจาก COMPOSE_FILE |
เปิด http://127.0.0.1:8080/admin/ แล้วได้ 404 |
cat deploy/Caddyfile |
เป็นพฤติกรรมที่ถูกต้อง — Caddyfile ปกติส่งทุก path ไป api · ใช้ 127.0.0.1:8081 แทน หรือเปิด overlay Cloudflare |
เปิด :8081/admin/ แล้วได้ 404 ทุกหน้า |
docker compose exec admin env | grep CORETH_ADMIN_ENABLED |
admin_access() โยน 404 เมื่อ CORETH_ADMIN_ENABLED ไม่ใช่ true · ต้องเข้าผ่าน service admin ไม่ใช่ api |
| ล็อกอินได้แต่ทุกหน้า Admin ขึ้น 404 | มี AdminMembership ที่ active=true ไหม |
รัน bootstrap_root_user ใหม่ หรือเพิ่มสมาชิกที่ /admin/users · ตรวจว่า customer_id และ environment ตรงกับ .env |
migrate ล้มเพราะต่อฐานไม่ได้ |
docker compose logs migrate |
ฐานภายนอกต้องใช้ SSL และ URL-encode รหัสผ่าน · รัน docker compose run --rm wizard ใหม่ซึ่งจะ SELECT 1 ก่อนบันทึก |
Wizard บอก Configuration changed during setup; rerun wizard |
มีใครแก้ .env พร้อมกันหรือไม่ |
เป็นการป้องกันเขียนทับ · ปิดตัวแก้ไขอื่นแล้วรัน wizard ใหม่ |
9.2 ความพร้อม (readiness)
| อาการ | ตรวจ | แก้ |
|---|---|---|
/health/ready → 503 CUSTOMER_NOT_CONFIGURED |
grep CORETH_DEPLOYMENT_CUSTOMER_ID .env |
ค่าว่างหรือไม่ใช่ UUID · แก้แล้ว docker compose up -d --force-recreate api |
/health/ready → 503 DOCTOR_NOT_EVALUATED |
docker compose ps worker |
worker ยังไม่เคยรัน · docker compose up -d worker (Doctor รันอยู่ใน runworker เท่านั้น) |
/health/ready → 503 DOCTOR_STALE |
docker compose logs --tail=30 worker |
worker ตายหรือค้าง · ผลเก่ากว่า CORETH_DOCTOR_MAX_AGE_SECONDS (300 วิ) · รีสตาร์ต worker และดู exception |
phase ค้างที่ SCANNING |
context_status ดู initial_build_status |
Initial Context Build ยังไม่จบ · รัน onboard_file_ingestion จนได้ IMPORTED |
phase DEGRADED เพราะ BACKUP_HEALTH |
มี CORETH_BACKUP_KEY_* หรือยัง |
ตั้งคีย์แล้วรัน backup_context + restore_rehearsal หรือเปิด --profile ops service backup |
worker heartbeat เป็น DOCTOR_ERROR |
หา context_doctor_failed ใน log |
Doctor โยน exception ระหว่างประเมิน · ดู error_code ใน log JSON |
9.3 การนำเข้าและประมวลผลไฟล์
| Error code | ความหมาย | แก้ |
|---|---|---|
AGENTIC_REQUIRED | source ไม่ได้ตั้ง semantic_processing.mode = AI_REQUIRED ขณะที่ CORETH_AGENTIC_ONLY=true | แก้ source config (สร้าง config_version ใหม่) หรือปิด CORETH_AGENTIC_ONLY ชั่วคราวระหว่างย้ายระบบ |
AI_QUOTA_EXHAUSTED | เครดิต/โควตา OpenAI หมด · ระบบสร้าง AISourcePause อัตโนมัติเพื่อไม่ทิ้งงาน | เติมเครดิต แล้ว resume_ai --source-id … --customer-id … (ดูรายการด้วย resume_ai เปล่า ๆ) |
AI_SECRET_KEY_MISSING / AI_SECRET_UNAVAILABLE | อ่าน secret ไม่ได้ หรือใน secret ไม่มี key OPENAI_API_KEY | ตรวจรูปแบบ JSON ของ secret และสิทธิ์ secretsmanager:GetSecretValue + kms:Decrypt |
AI_FILE_TOO_LARGE | ไฟล์ใหญ่กว่า CORETH_FILE_AGENT_MAX_BYTES (5 MB) | เพิ่มค่าใน .env หรือแยกไฟล์ต้นทาง · ระบบไม่ตัดเนื้อหาทิ้งเงียบ ๆ โดยเจตนา |
AI_PDF_LIMIT_OR_ENCRYPTED | PDF เกิน CORETH_FILE_AGENT_MAX_PAGES หรือถูกใส่รหัส | ปลดรหัส/แยกไฟล์ หรือเพิ่มค่า max pages |
AI_RAW_FILE_REDACTION_REQUIRED | policy บังคับปกปิดข้อมูล แต่ PDF/ภาพปกปิดไม่ได้ | เปิด “อนุญาตส่ง PDF/ภาพต้นฉบับให้ AI” ที่ /admin/setup หรือเอา redaction rule ออก |
FILE_INGESTION_HTTP_<status>(เช่น …_401, …_403) | File Ingestion ตอบไม่ใช่ 200 · ระบบไม่เปิดเผย body/URL ของต้นทางใน diagnostics | ตรวจ issuer/audience/subject/kid/private_key ใน secret และสิทธิ์ file:read, events:read |
ARCHIVE_HTTP_403 | subject ยังไม่มีสิทธิ์ file:archive-openwebui ที่ File Ingestion หรือ subject ใน secret ไม่ตรงกับที่ให้สิทธิ์ไว้ | รันบนเครื่อง File Ingestion: docker exec -it coreth-file-ingestion-api-1 python manage.py configure_openwebui_archive --subject service:context-processor แล้วตรวจว่า subject ใน secret ตรงกัน |
ARCHIVE_HTTP_<status> อื่น ๆ | ขาอัปโหลดไม่ได้ HTTP 202 | 401 = JWT ผิด/หมดอายุ · 413 = ไฟล์ใหญ่เกินขีดของ File Ingestion · 5xx = ปลายทางมีปัญหา ระบบจะ retry ให้เอง |
receipt ค้าง PROCESSING / RETRY ไม่จบสักที | อัปโหลดผ่านแล้ว (มี archive_file_version_id) แต่ขาตรวจซ้ำล้ม — มักเพราะ subject มีแค่ file:archive-openwebui ไม่มี file:read | ให้สิทธิ์ file:read + events:read กับ subject เดียวกัน |
ARCHIVE_REFERENCE_MISMATCH | source_provenance ของ file version ไม่ตรงกับ submission ต้นทาง | ตรวจว่า File Ingestion บันทึก provenance ครบทั้ง 6 ช่องตามตารางในหัวข้อ 6.2 |
ARCHIVE_CUSTOMER_MISMATCH / ARCHIVE_CHECKSUM_MISMATCH | ปลายทางตอบ customer หรือ sha256 ไม่ตรงกับที่ส่งไป | หยุดใช้ endpoint นั้นแล้วตรวจว่า CORETH_OPENWEBUI_ARCHIVE_URL ชี้ไป File Ingestion ของ customer ที่ถูกต้อง |
INPUT_CHECKSUM_MISMATCH | เนื้อหาที่เก็บไว้ไม่ตรง checksum ตอนอัปโหลด | ให้ผู้ใช้อัปโหลดใหม่ · submission เดิมใช้ต่อไม่ได้ |
ONBOARDING_INCOMPLETE | ครบ --max-cycles แต่ยังไม่หมดคิว | รัน onboard_file_ingestion ซ้ำ — ทำต่อจากเดิมได้เพราะมี cursor |
FORCE_STOPPED_BY_OPERATOR | มีคนกดหยุดงานนั้นจาก Admin | สั่งลองใหม่ที่ /admin/ingestion-history → retry |
SNAPSHOT_STORAGE_NOT_CONFIGURED | policy ต้องการ replay snapshot แต่ไม่มี bucket/KMS | ตั้ง CORETH_SNAPSHOT_BUCKET + CORETH_SNAPSHOT_KMS_KEY_ID (บังคับใช้ SSE-KMS) |
9.4 การตรวจทานและ schema
| Error code | ความหมาย | แก้ |
|---|---|---|
409 REVISION_CONFLICT | candidate เปลี่ยนไปแล้วระหว่างที่กำลังตรวจ | โหลด manifest ใหม่แล้วยืนยันอีกครั้ง |
409 SCHEMA_BASE_CHANGED | มีข้อเสนออื่นถูกอนุมัติก่อน และ rebase อัตโนมัติไม่ปลอดภัย | สั่ง regroup หรือให้ AI เสนอใหม่บน base ปัจจุบัน |
409 SCHEMA_ALREADY_DECIDED | มีคนตัดสินข้อเสนอนี้ไปแล้ว | รีเฟรชหน้า |
409 FEEDBACK_APPLY_PENDING | AI กำลังแก้ข้อเสนอตาม feedback อยู่ (REBUILDING) | รอ worker ทำ ReviewJob(FEEDBACK_APPLY) จบ |
IDENTITY_MIGRATION_REVIEW_REQUIRED | การอนุมัติจะเปลี่ยน identity field ของแถวที่มีอยู่ | ตรวจแผน migration แล้วส่ง migration_digest ที่ตรงกันมายืนยัน |
DESIGN_PLAN_DECISION_REQUIRED | ข้อเสนอนี้อยู่ภายใต้ SchemaDesignPlan | ตัดสินผ่านหน้า plan ไม่ใช่ตัดสินข้อเสนอเดี่ยว |
HUMAN_UPLOAD_CONFIRMATION_REQUIRED | ไฟล์จาก OpenWebUI ต้องมีคนยืนยันเสมอ | ไม่ใช่ข้อผิดพลาด — ให้ผู้ใช้เข้าไปกดยืนยัน |
AUTO_NEW_DISABLED / AUTO_NEW_SOURCE_DISABLED | ปิด auto-approve ที่ระดับ deployment หรือระดับ source | ตรวจ CORETH_AUTO_APPROVE_NEW_DATA, CORETH_REVIEW_PAUSE_APPLY และ structured_data.auto_approve_new_data |
AUTO_NEW_SOURCE_CHANGED / ROW_APPROVAL_STALE | source config หรือ policy เปลี่ยนหลังออกใบอนุมัติแถว | ทำงานถูกต้องตามออกแบบ — ให้ worker สร้างการอนุมัติใหม่บนนโยบายปัจจุบัน |
SUBMISSION_GRANT_REQUIRED / SOURCE_WRITE_SCOPE_REQUIRED | Service JWT ไม่มี scope หรือ grant หมดอายุ | manage_tool_grant configure ใหม่ · ตรวจ expires_at และ claim source_id ใน token |
HUMAN_SESSION_REQUIRED | พยายามกดยืนยันด้วย Service JWT | การยืนยันขั้นสุดท้ายต้องเป็น session ของคนเท่านั้น |
LEGACY_CENTER_MAPPING_REQUIRED | dataset เดิมถูกรับเป็น baseline โดยผูกกับศูนย์หนึ่ง แต่ข้อมูลใหม่มาจากศูนย์อื่น | แยก dataset หรือย้าย identity ด้วยแผน migration |
| ไม่มีอะไรเผยแพร่เลยทั้งที่ยืนยันแล้ว | grep CORETH_REVIEW_PAUSE_APPLY .env | ถ้าเป็น true คือเปิดโหมดหยุดทั้งหมดไว้ · run_review_jobs() return ทันที |
9.5 ข้อมูลและฐานข้อมูล
| อาการ | ตรวจ | แก้ |
|---|---|---|
| ข้อมูลเผยแพร่แล้วแต่ Context ยังไม่ขยับ | Doctor check DATA_OUTBOX_PROJECTION | ต้องมี ContextProjectionDefinition ที่ active มิฉะนั้น event ถูกทำเป็น DELIVERED ทิ้ง · สร้างด้วย configure_context_projection |
ตาราง app_output ไม่มีคอลัมน์ใหม่ | อนุมัติ SchemaImprovement แล้วหรือยัง | DDL สร้างตอน prepare_output() ซึ่งรันเมื่ออนุมัติ schema เท่านั้น |
| Supabase REST เห็นตารางภายในของระบบ | รัน SQL ตรวจ RLS ในหัวข้อ 7.5 | docker compose exec api python manage.py secure_database |
แถวติด DataConflict ไม่เผยแพร่ | /admin/data-conflicts | ตัดสินทีละฟิลด์ หรือกำหนดลำดับความน่าเชื่อถือด้วย configure_source_authority |
| TransformRun ค้างสถานะ RUNNING | Doctor check TRANSFORM_RUNS | recover_interrupted_transforms — ไม่ลบข้อมูลที่ commit แล้ว |
เปลี่ยนค่าใน .env แล้วไม่มีผล | ตัวแปรนั้นอยู่ในบล็อก &app-env ของ compose.yaml ไหม | ดูรายการในหัวข้อ 5.6 · ถ้าไม่อยู่ ต้องเพิ่มเองใน compose.yaml |
10. Differences from Legacy Documentation#
เฉพาะข้อที่เปลี่ยนอย่างมีนัยสำคัญและพบระหว่างอ่านโค้ดจริงที่ commit 8d2a865
ไม่ใช่ changelog ทั้งหมด
| เอกสารเดิมบอกว่า | ความจริงในโค้ดวันนี้ |
|---|---|
เอกสารสถาปัตยกรรม v1.1 (ลบแล้ว) อธิบาย streaming CSV pipeline (process_stream, StreamingIngestionCheckpoint) เป็นเส้นทางหลักของไฟล์ขนาดใหญ่ |
โค้ดยังอยู่ แต่ เข้าไม่ถึงเลย ด้วยค่าเริ่มต้น — sync.py เรียกเมื่อ not settings.CORETH_AGENTIC_ONLY ซึ่งค่าเริ่มต้นเป็น true |
โหมด semantic processing มีหลายแบบ (DETERMINISTIC_ONLY, AI_OPTIONAL, AI_REQUIRED) และไฟล์ตัวอย่างใน examples/file-ingestion/ ใช้ DETERMINISTIC_ONLY |
เหลือ AI_REQUIRED ทางเดียวในทางปฏิบัติ · อย่างอื่นทำให้ signal fail ด้วย AGENTIC_REQUIRED · ไฟล์ตัวอย่างล้าสมัยแล้ว |
| ไม่มีการกล่าวถึง Submission / Review workflow | มีครบเป็นแกนหลัก: Submission, CandidateGroup, CandidateRow, ReviewReceipt, RowApproval, ReviewJob, BatchReview พร้อม API และหน้า Admin |
| ไม่มี File Agent | coreth_data/file_agent.py ใช้ OpenAI Agents SDK อ่าน schema เดิมแล้วเสนอ table/row · เป็นเส้นทางประมวลผลหลักของ FILE_READY และ OPENWEBUI_UPLOAD |
| ไม่มี OpenWebUI ในสถาปัตยกรรม | เป็น source type เต็มรูปแบบ (OPENWEBUI_UPLOAD) มี Service JWT, ToolServiceGrant, OpenWebUIUploadReceipt, หน้าตรวจทานเฉพาะผู้ใช้ และการส่งไฟล์ดิบกลับไปเก็บที่ File Ingestion |
| ไม่มี Center / ขอบเขตการตรวจทาน | Center + CenterMembership + SourceBinding เป็นตัวกำหนดว่าใครตรวจข้อมูลของศูนย์ไหนได้ · จัดการที่ /admin/centers |
| ไม่มี Schema Design ล่วงหน้า | SchemaDesignPlan, SchemaDesignSource, API /api/v1/schema-design และวงจร feedback ให้ AI แก้ข้อเสนอ |
เอกสารติดตั้งเดิมพาไปที่ /admin ผ่าน port หลัก |
Monitor เป็น service แยก (admin) ที่ 127.0.0.1:8081 · deploy/Caddyfile ปกติไม่ route /admin · เฉพาะ Caddyfile.cloudflare เท่านั้นที่ route ไป admin:8000 |
เริ่มทุกอย่างด้วย docker compose up -d |
worker อยู่ใน profile แยกโดยเจตนา ต้องสั่ง docker compose up -d worker ต่างหาก และห้ามใส่ไว้ใน COMPOSE_PROFILES |
| Readiness เป็นเรื่องของ API | ContextDoctor รันอยู่ใน runworker · ถ้าไม่มี worker ระบบจะไม่มีวันขึ้น READY |
.env.advanced.example เสนอให้ปรับ DJANGO_DEBUG, DATABASE_URL, CORETH_LOG_LEVEL, CORETH_BACKUP_DIRECTORY, DATABASE_CONN_MAX_AGE และชุด CORETH_*_VERSION |
ตัวแปรกลุ่มนี้ไม่ได้อยู่ในบล็อก &app-env ของ compose.yaml จึงไปไม่ถึง container · ดูหัวข้อ 5.6 |
| เอกสารสถาปัตยกรรม v1.1 ระบุว่ามี “deliberate current limits” หลายข้อ | หลายข้อถูกแทนที่ด้วย governance workflow แล้ว · อ่านข้อจำกัดจาก CORETH_AUTO_APPROVE_* และ CORETH_REVIEW_PAUSE_APPLY ในหัวข้อ 5.3 แทน |
เอกสารที่เหลืออยู่ใน docs/
เอกสารที่เนื้อหาถูกยกมารวมไว้ในฉบับนี้แล้วถูกลบทิ้ง (24 ไฟล์ · ยังกู้คืนได้จาก git history) ที่เหลือคือเอกสารที่ฉบับนี้ไม่ได้ครอบคลุม
| ไฟล์ | ใช้อ้างอิงเรื่องอะไร |
|---|---|
docs/adr/0001–0003 | เหตุผลเบื้องหลังการตัดสินใจเชิงสถาปัตยกรรม · เป็นหลักฐานทางประวัติศาสตร์ ไม่แก้ย้อนหลัง และ scripts/rollback-preflight.sh ต้องอ้างหมายเลข ADR |
docs/review-workflow/ · schema-review-feedback.md · review-validation-recovery.md · review-row-removal-and-text-numbers.md | รายละเอียดการตรวจทานและวงจร feedback ที่ลึกกว่า §2.4–2.5 |
docs/file-agent-workflow-th.md | พฤติกรรม File Agent ทีละขั้น ลึกกว่า §2.3 |
docs/openwebui-single-account-review-th.md | เคสใช้ OpenWebUI บัญชีเดียว |
docs/file-ingestion-sales-validation-th.md | เกณฑ์ตรวจรับ Gmail CSV → Supabase สองฐาน |
docs/cleanup-file-ingestion-th.md | คู่มือของ scripts/cleanup_file_ingestion_transactions.py |
docs/stationrich-*.md | งานเฉพาะลูกค้า Station Rich |
docs/ เป็นเอกสารประกอบเฉพาะเรื่อง
เมื่อขัดกันให้ยึดฉบับนี้ และเมื่อฉบับนี้ขัดกับโค้ด ให้ยึดโค้ดแล้วแก้เอกสาร