Files
knowledge-governance/internal/qaopsai/batch_actions.go
T
姚章浩 533db535f1
build / build (push) Successful in 1m12s
feat(qaops): 接入真实模型补缺任务与总表看板
按已确认需求提供独立安全模型配置、真实探测、正式字典字段评估与原问题译中,持久批次支持只补空白、暂停恢复、有限重试、幂等和逐字段依据。覆盖固定规则、本地语言、组织关联与主表有效看板共用治理侧读模型,保留源生产和冻结历史。

验证:完整 make verify 退出0,702项Go与329项前端通过;默认118项外部/隔离集成明确跳过,另跑完整镜像及组织隔离PG竞态。12并发、输入漂移、过期租约、暂停/恢复及有限重试通过。用户授权的百炼 deepseek-v4.1-flash 完成真实后台与浏览器评估/翻译;552行、16列、11块看板及完整PNG、独立配置页测试/保存、重开恢复和零JS异常通过。仅非业务样本外发,自动处理关闭,原payload不变。

上线前治理库已做导出快照原生备份并真实恢复,60表行数与17个QA表内容哈希一致;不修改DobotAI生产或隧道。本提交时尚未部署,线上验收须独立完成。
2026-10-19 14:29:49 +08:00

85 lines
4.2 KiB
Go

package qaopsai
import (
"context"
"github.com/jackc/pgx/v5"
"strings"
)
// ManageBatch serializes with claims; already reserved requests settle, but later claims stop.
func (s *Store) ManageBatch(ctx context.Context, id, action, nonce, actor string) (Batch, error) {
if !s.valid() || id == "" || actor == "" || len(nonce) < 16 || len(nonce) > 128 || strings.ContainsRune(nonce, 0) || (action != "pause" && action != "resume" && action != "retry") {
return Batch{}, ErrInvalid
}
tx, err := s.pool.Begin(ctx)
if err != nil {
return Batch{}, ErrUnavailable
}
defer tx.Rollback(context.Background())
if _, err = tx.Exec(ctx, `SELECT pg_advisory_xact_lock(hashtextextended($1,57))`, s.tenant+":"+actor+":"+nonce); err != nil {
return Batch{}, ErrUnavailable
}
var priorID, priorAction string
err = tx.QueryRow(ctx, `SELECT batch_id,action FROM qa_ops_ai_batch_actions WHERE tenant_key=$1 AND actor=$2 AND nonce=$3`, s.tenant, actor, nonce).Scan(&priorID, &priorAction)
if err == nil {
if priorID != id || priorAction != action {
return Batch{}, ErrConflict
}
_ = tx.Rollback(ctx)
return s.GetBatch(ctx, id)
}
if err != pgx.ErrNoRows {
return Batch{}, ErrUnavailable
}
if _, err = tx.Exec(ctx, `INSERT INTO qa_ops_ai_runtime(tenant_key)VALUES($1)ON CONFLICT DO NOTHING`, s.tenant); err != nil {
return Batch{}, ErrUnavailable
}
var locked string
if tx.QueryRow(ctx, `SELECT tenant_key FROM qa_ops_ai_runtime WHERE tenant_key=$1 FOR UPDATE`, s.tenant).Scan(&locked) != nil {
return Batch{}, ErrUnavailable
}
if err = tx.QueryRow(ctx, `SELECT id FROM qa_ops_ai_batches WHERE tenant_key=$1 AND id=$2 FOR UPDATE`, s.tenant, id).Scan(&locked); err == pgx.ErrNoRows {
return Batch{}, ErrInvalid
} else if err != nil {
return Batch{}, ErrUnavailable
}
state := "paused"
if action != "pause" {
state = "active"
}
if _, err = tx.Exec(ctx, `UPDATE qa_ops_ai_batches SET state=$3 WHERE tenant_key=$1 AND id=$2`, s.tenant, id, state); err != nil {
return Batch{}, ErrUnavailable
}
if action == "pause" {
_, err = tx.Exec(ctx, `UPDATE qa_ops_ai_work w SET state='paused',stage='paused',updated_at=clock_timestamp() WHERE w.tenant_key=$1 AND w.trigger_mode='manual' AND w.state IN ('queued','retry') AND EXISTS(SELECT 1 FROM qa_ops_ai_batch_items i WHERE i.tenant_key=w.tenant_key AND i.work_id=w.id AND i.batch_id=$2) AND NOT EXISTS(SELECT 1 FROM qa_ops_ai_batch_items i JOIN qa_ops_ai_batches b ON b.tenant_key=i.tenant_key AND b.id=i.batch_id WHERE i.tenant_key=w.tenant_key AND i.work_id=w.id AND b.state='active')`, s.tenant, id)
} else if action == "retry" {
cfg, e := s.readSettings(ctx, tx)
if e != nil {
return Batch{}, e
}
if cfg.Verification != "verified" || !cfg.StorageReady {
return Batch{}, ErrConflict
}
var stale bool
if tx.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM qa_ops_ai_work w JOIN qa_ops_ai_batch_items i ON i.tenant_key=w.tenant_key AND i.work_id=w.id WHERE w.tenant_key=$1 AND i.batch_id=$2 AND w.state='failed' AND w.config_revision<>$3)`, s.tenant, id, cfg.Revision).Scan(&stale) != nil {
return Batch{}, ErrUnavailable
}
if stale {
return Batch{}, ErrConflict
}
_, err = tx.Exec(ctx, `UPDATE qa_ops_ai_work w SET state='queued',stage='queued',retry_base=attempt,available_at=clock_timestamp(),finished_at=NULL,error_code='',skip_reason='',updated_at=clock_timestamp() WHERE w.tenant_key=$1 AND w.state='failed' AND EXISTS(SELECT 1 FROM qa_ops_ai_batch_items i WHERE i.tenant_key=w.tenant_key AND i.work_id=w.id AND i.batch_id=$2)`, s.tenant, id)
} else {
_, err = tx.Exec(ctx, `UPDATE qa_ops_ai_work w SET state='queued' ,stage='queued',updated_at=clock_timestamp() WHERE w.tenant_key=$1 AND w.trigger_mode='manual' AND w.state='paused' AND EXISTS(SELECT 1 FROM qa_ops_ai_batch_items i WHERE i.tenant_key=w.tenant_key AND i.work_id=w.id AND i.batch_id=$2)`, s.tenant, id)
}
if err != nil {
return Batch{}, ErrUnavailable
}
if _, err = tx.Exec(ctx, `INSERT INTO qa_ops_ai_batch_actions(tenant_key,actor,nonce,batch_id,action)VALUES($1,$2,$3,$4,$5)`, s.tenant, actor, nonce, id, action); err != nil {
return Batch{}, ErrUnavailable
}
if tx.Commit(ctx) != nil {
return Batch{}, ErrUnavailable
}
return s.GetBatch(ctx, id)
}