Eskalasi otomatis dari bot, sistem evaluasi machine learning, atau background worker ke antrean review manual (Human-in-the-Loop / HITL) sering kali memicu dua masalah utama: queue churn akibat payload yang tidak memiliki konteks diagnostik memadai, serta race condition saat operator mengeklaim tiket secara bersamaan. Tanpa kontrak API yang ketat, operator manusia menghabiskan waktu merekonstruksi status sistem, atau mengerjakan tiket yang sudah diambil operator lain.
1. Desain Payload: Mewajibkan Context Proof
Penyebab utama queue churn adalah pengiriman payload minimal seperti {"error": "failed", "entity_id": 102}. Operator tidak dapat mengambil keputusan tanpa riwayat eksekusi. Kontrak eskalasi harus mewajibkan context proof bundle dan precheck hash.
Precheck hash merupakan kalkulasi SHA-256 dari langkah self-healing otomatis yang gagal. Hal ini membuktikan bahwa agen atau background worker telah menjalankan prosedur validasi lokal sebelum membuang beban kerja ke manusia.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "HITLEscalationRequest",
"type": "object",
"required": [
"escalation_id",
"idempotency_key",
"entity_type",
"entity_id",
"reason_code",
"precheck_hash",
"diagnostic_bundle"
],
"properties": {
"escalation_id": { "type": "string", "format": "uuid" },
"idempotency_key": { "type": "string", "minLength": 16 },
"entity_type": { "type": "string" },
"entity_id": { "type": "string" },
"reason_code": { "type": "string" },
"precheck_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"diagnostic_bundle": {
"type": "object",
"required": ["trace_id", "attempted_actions", "input_snapshot", "evaluator_verdict"],
"properties": {
"trace_id": { "type": "string" },
"attempted_actions": {
"type": "array",
"items": {
"type": "object",
"required": ["action", "executed_at", "result"],
"properties": {
"action": { "type": "string" },
"executed_at": { "type": "string", "format": "date-time" },
"result": { "type": "string" }
}
}
},
"input_snapshot": { "type": "object" },
"evaluator_verdict": {
"type": "object",
"required": ["confidence_score", "threshold"],
"properties": {
"confidence_score": { "type": "number" },
"threshold": { "type": "number" }
}
}
}
}
}
}2. Idempotensi dan Pencegahan Escalation Storm
Saat terjadi kegagalan berseri, sistem otomatis cenderung mengirimkan ulang payload eskalasi pada setiap retry loop. Tanpa deduplikasi, antrean manual terdistorsi oleh duplikasi entity yang sama.
Gunakan kombinasi database unique constraint dan Redis short-lived lock:
- Unique constraint: Gabungan
(entity_type, entity_id)dibatasi hanya boleh memiliki satu record berstatus aktif (PENDING_REVIEWatauCLAIMED). - Idempotency key: Endpoint eskalasi menyimpan
idempotency_keydengan masa kedaluwarsa (TTL) 24 jam untuk mendeteksi re-entrant payload identik.
CREATE TABLE review_tasks (
id UUID PRIMARY KEY,
idempotency_key VARCHAR(64) NOT NULL UNIQUE,
entity_type VARCHAR(64) NOT NULL,
entity_id VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'PENDING_REVIEW',
precheck_hash CHAR(64) NOT NULL,
diagnostic_bundle JSONB NOT NULL,
version INT NOT NULL DEFAULT 1,
claimed_by VARCHAR(64),
lease_token UUID,
lease_expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Mencegah duplikasi tugas aktif untuk entitas yang sama
CREATE UNIQUE INDEX uq_active_entity_task
ON review_tasks (entity_type, entity_id)
WHERE status IN ('PENDING_REVIEW', 'CLAIMED');3. Finite State Machine (FSM) dan State Lock
Antrean HITL melibatkan tiga status utama:
PENDING_REVIEW: Tersedia dalam antrean untuk dikerjakan.CLAIMED: Sedang dievaluasi oleh operator. Terikat padalease_tokenberdurasi terbatas (misal: 15 menit) untuk mencegah tiket tertahan saat operator menutup browser tanpa submit.RESOLVED: Keputusan selesai, hasil dicatat, payload diteruskan ke callback.
Mekanisme Atomic Claiming
Jangan gunakan alur SELECT lalu UPDATE terpisah. Gunakan conditional update langsung pada database untuk menjamin atomic operation tanpa distributed transaction overhead.
-- Mengklaim tiket dengan lease lock 15 menit
UPDATE review_tasks
SET
status = 'CLAIMED',
claimed_by = :operator_id,
lease_token = :generated_token,
lease_expires_at = NOW() + INTERVAL '15 minutes',
version = version + 1,
updated_at = NOW()
WHERE id = :task_id
AND (
status = 'PENDING_REVIEW'
OR (status = 'CLAIMED' AND lease_expires_at < NOW())
);
-- ponytail: jika returning rows = 0, tiket telah diambil atau lease masih valid di operator lain.4. Route Handler: Validasi Kontrak & State Transition
Implementasi Express/TypeScript untuk endpoint eskalasi dan klaim:
import { Request, Response, Router } from 'express';
import crypto from 'crypto';
import { db } from './db';
const router = Router();
// Endpoint Eskalasi Task
router.post('/tasks/escalate', async (req: Request, res: Response) => {
const { escalation_id, idempotency_key, entity_type, entity_id, precheck_hash, diagnostic_bundle } = req.body;
// 1. Validasi keberadaan context proof minimum
if (!diagnostic_bundle?.attempted_actions?.length || !diagnostic_bundle?.evaluator_verdict) {
return res.status(422).json({
error_code: 'ERR_INSUFFICIENT_CONTEXT_PROOF',
message: 'Eskalasi ditolak: diagnostic bundle wajib mencakup trace, attempted actions, dan verdict.',
missing_fields: ['diagnostic_bundle.attempted_actions', 'diagnostic_bundle.evaluator_verdict']
});
}
// 2. Validasi integritas precheck hash
const computedHash = crypto
.createHash('sha256')
.update(JSON.stringify(diagnostic_bundle.attempted_actions))
.digest('hex');
if (computedHash !== precheck_hash) {
return res.status(422).json({
error_code: 'ERR_PRECHECK_HASH_MISMATCH',
message: 'Hash diagnostik pre-check tidak valid dengan bukti aksi yang disertakan.'
});
}
try {
await db.query(
`INSERT INTO review_tasks (id, idempotency_key, entity_type, entity_id, precheck_hash, diagnostic_bundle)
VALUES ($1, $2, $3, $4, $5, $6)`,
[escalation_id, idempotency_key, entity_type, entity_id, precheck_hash, JSON.stringify(diagnostic_bundle)]
);
return res.status(201).json({ status: 'PENDING_REVIEW', task_id: escalation_id });
} catch (err: any) {
if (err.code === '23505') { // Unique violation Postgres
return res.status(409).json({
error_code: 'ERR_TASK_CONFLICT',
message: 'Task aktif untuk entitas ini sudah terdaftar dalam antrean.'
});
}
return res.status(500).json({ error_code: 'ERR_INTERNAL_SERVER' });
}
});
// Endpoint Klaim Task oleh Operator
router.post('/tasks/:id/claim', async (req: Request, res: Response) => {
const { id } = req.params;
const { operator_id } = req.body;
const leaseToken = crypto.randomUUID();
const result = await db.query(
`UPDATE review_tasks
SET status = 'CLAIMED', claimed_by = $1, lease_token = $2, lease_expires_at = NOW() + INTERVAL '15 minutes', version = version + 1, updated_at = NOW()
WHERE id = $3 AND (status = 'PENDING_REVIEW' OR (status = 'CLAIMED' AND lease_expires_at < NOW()))
RETURNING id, lease_token, lease_expires_at`,
[operator_id, leaseToken, id]
);
if (result.rowCount === 0) {
return res.status(409).json({
error_code: 'ERR_STATE_CONFLICT',
message: 'Tiket sudah diklaim oleh operator lain atau sedang dalam proses penyelesaian.'
});
}
return res.status(200).json(result.rows[0]);
});
export default router;5. Penyelesaian dan Webhook Callback Contract
Saat operator menyelesaikan review, payload keputusan dikirim melalui POST /tasks/:id/resolve. Sistem memverifikasi lease_token milik operator yang bersangkutan, mengubah status menjadi RESOLVED, lalu memicu webhook ke sistem sumber secara asynchronous.
POST /tasks/a7b1c4e2-8921-4f1b-b461-71e9c20a9a12/resolve
Content-Type: application/json
X-Lease-Token: 9e32a688-6628-4e56-91e8-636279f1dc85
{
"operator_id": "usr_op_771",
"decision": "REJECTED",
"notes": "Anomali format transaksi melanggar rule AML-04.",
"resolution_payload": {
"override_action": "BLOCK_TRANSACTION",
"freeze_account": false
}
}Respons dari webhook callback ke consumer harus menyertakan X-Signature-256 berbasis HMAC SHA-256 untuk memvalidasi bahwa payload benar-benar berasal dari sistem HITL internal, bukan injeksi eksternal.
Aturan Konkurensi: Jangan izinkan ekstensi lease tanpa verifikasi token aktif. Jika masa lease habis, operator tidak boleh mengirimkan penyelesaian sebelum melakukan pembaruan lease secara eksplisit.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!