Files
knowledge-governance/internal/qaopsmirror/master_fields.go
T
姚章浩 2c0bb32183
build / build (push) Successful in 1m11s
fix(qaops): 点踩问题统一判为不正确
通过持久来源关联读取最新本地反馈,强制总表有效正确性为否,保留原问答和人工证据。统一列表、详情、指标和两阶段读取的反馈快照,拒绝冲突选择并保留其他字段编辑。已通过完整本地门禁、隔离PG竞态和真实恢复库52条点踩逐条读回、桌面及手机验收;未调用模型或连接源生产库。
2026-10-20 08:18:03 +08:00

156 lines
7.6 KiB
Go

package qaopsmirror
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"github.com/jackc/pgx/v5"
"knowledge-governance/internal/dobotaiops"
"knowledge-governance/internal/qaopsfields"
"knowledge-governance/internal/qaopsquality"
"regexp"
"strings"
"time"
"unicode/utf8"
)
type MasterFieldPatch struct {
InputHash string `json:"input_hash"`
InputEpoch int64 `json:"input_epoch"`
BaseRevision int64 `json:"base_revision"`
BaseReviewRevision int64 `json:"base_review_revision"`
Nonce string `json:"nonce"`
Fields map[string]string `json:"fields"`
}
type MasterFieldReceipt struct {
SavedRevision int64 `json:"saved_revision"`
Replayed bool `json:"replayed"`
}
var inlineHash = regexp.MustCompile(`^[a-f0-9]{64}$`)
var inlineNonce = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.:-]{15,127}$`)
func validInlineID(value string) bool {
return value != "" && len(value) <= 256 && utf8.ValidString(value) && !strings.ContainsAny(value, "\x00\x1f")
}
func (s *Store) PatchMasterFields(ctx context.Context, id string, q MasterFieldPatch, actor string) (MasterFieldReceipt, error) {
if !s.valid() {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
if !validInlineID(id) || !validInlineID(actor) || !inlineHash.MatchString(q.InputHash) || !inlineNonce.MatchString(q.Nonce) || q.InputEpoch < 1 || q.BaseRevision < 0 || q.BaseReviewRevision < 0 || len(q.Fields) < 1 || len(q.Fields) > 5 {
return MasterFieldReceipt{}, dobotaiops.ErrInvalidQuery
}
for field, value := range q.Fields {
if !qaopsfields.ValidEditableValue(field, value) {
return MasterFieldReceipt{}, dobotaiops.ErrInvalidQuery
}
}
ctx, cancel := context.WithTimeout(ctx, 8*time.Second)
defer cancel()
tx, err := s.pool.Begin(ctx)
if err != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
defer tx.Rollback(context.Background())
if _, err = tx.Exec(ctx, `SELECT pg_advisory_xact_lock(hashtextextended($1||chr(31)||$2,258))`, s.tenant, id); err != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
payload, _ := json.Marshal(q)
sum := sha256.Sum256(payload)
requestHash := hex.EncodeToString(sum[:])
var savedRevision int64
var savedHash, savedActor string
err = tx.QueryRow(ctx, `SELECT revision,request_hash,actor FROM qa_ops_master_field_edits WHERE tenant_key=$1 AND record_id=$2 AND nonce=$3`, s.tenant, id, q.Nonce).Scan(&savedRevision, &savedHash, &savedActor)
if err == nil {
if savedHash != requestHash || savedActor != actor {
return MasterFieldReceipt{}, qaopsquality.ErrConflict
}
return MasterFieldReceipt{SavedRevision: savedRevision, Replayed: true}, nil
}
if !errors.Is(err, pgx.ErrNoRows) {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
var hash string
var reviewRevision, epoch int64
err = tx.QueryRow(ctx, `SELECT input_hash,review_revision,input_epoch FROM qa_ops_quality_records WHERE tenant_key=$1 AND record_id=$2 FOR UPDATE`, s.tenant+"::master", id).Scan(&hash, &reviewRevision, &epoch)
if errors.Is(err, pgx.ErrNoRows) {
return MasterFieldReceipt{}, dobotaiops.ErrRecordNotFound
}
if err != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
if hash != q.InputHash || epoch != q.InputEpoch || reviewRevision != q.BaseReviewRevision {
return MasterFieldReceipt{}, qaopsquality.ErrConflict
}
var currentRevision, previousReview int64
var previousHash string
var raw []byte
overrides := map[string]string{}
err = tx.QueryRow(ctx, `SELECT revision,input_hash,review_revision,fields FROM qa_ops_master_field_overrides WHERE tenant_key=$1 AND record_id=$2`, s.tenant, id).Scan(&currentRevision, &previousHash, &previousReview, &raw)
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
if currentRevision != q.BaseRevision {
return MasterFieldReceipt{}, qaopsquality.ErrConflict
}
if previousHash == hash && previousReview == reviewRevision && json.Unmarshal(raw, &overrides) != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
var masterHash string
var fieldsRaw, coverageRaw, editsRaw, feedbackRaw []byte
err = tx.QueryRow(ctx, `SELECT quality_hash,effective_fields,coverage_result,inline_fields,current_feedback FROM qa_ops_master_read_model WHERE tenant_key=$1 AND id=$2`, s.tenant, id).Scan(&masterHash, &fieldsRaw, &coverageRaw, &editsRaw, &feedbackRaw)
if err != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
if masterHash != q.InputHash {
return MasterFieldReceipt{}, qaopsquality.ErrConflict
}
var fields, edits map[string]string
var coverage dobotaiops.CoverageCheck
var feedback dobotaiops.RecordFeedback
if json.Unmarshal(fieldsRaw, &fields) != nil || json.Unmarshal(editsRaw, &edits) != nil || json.Unmarshal(coverageRaw, &coverage) != nil || json.Unmarshal(feedbackRaw, &feedback) != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
if value, changed := q.Fields["is_correct"]; changed && value != "否" && qaopsquality.FeedbackForcesIncorrect(feedback) {
return MasterFieldReceipt{}, qaopsquality.ErrDownvoted
}
views, err := s.MasterQualityStore().ReadViewsInTx(ctx, tx, []dobotaiops.QualityKey{{ID: id, InputHash: masterHash}})
if err != nil {
return MasterFieldReceipt{}, err
}
current := qaopsquality.ProjectMasterWithEdits(views[id], fields, coverage, edits, feedback).Effective
beforeAll := map[string]string{"coverage": map[string]string{"covered": "是", "uncovered": "否", "partial": "部分覆盖", "pending": "待确认", "excluded": "否但不计入"}[current.Coverage], "is_correct": map[string]string{"correct": "是", "incorrect": "否", "pending": "待确认"}[current.Correctness], "product_category": current.ProductCategory, "problem_type": current.ProblemType, "root_cause": current.RootCause}
correctness := beforeAll["is_correct"]
if value, ok := q.Fields["is_correct"]; ok {
correctness = value
}
if correctness == "是" && q.Fields["root_cause"] != "" {
return MasterFieldReceipt{}, dobotaiops.ErrInvalidQuery
}
before := map[string]string{}
for key, value := range q.Fields {
before[key] = beforeAll[key]
overrides[key] = value
}
encoded, _ := json.Marshal(overrides)
changed, _ := json.Marshal(q.Fields)
previous, _ := json.Marshal(before)
revision := currentRevision + 1
if _, err = tx.Exec(ctx, `INSERT INTO qa_ops_master_field_overrides(tenant_key,record_id,revision,input_hash,review_revision,fields) VALUES($1,$2,$3,$4,$5,$6) ON CONFLICT(tenant_key,record_id) DO UPDATE SET revision=EXCLUDED.revision,input_hash=EXCLUDED.input_hash,review_revision=EXCLUDED.review_revision,fields=EXCLUDED.fields,updated_at=now()`, s.tenant, id, revision, hash, reviewRevision, encoded); err != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
if _, err = tx.Exec(ctx, `INSERT INTO qa_ops_master_field_edits(tenant_key,record_id,revision,input_hash,review_revision,changed_fields,before_fields,fields,actor,nonce,request_hash) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)`, s.tenant, id, revision, hash, reviewRevision, changed, previous, encoded, actor, q.Nonce, requestHash); err != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
tag, err := tx.Exec(ctx, `UPDATE qa_ops_master_records SET row_revision=row_revision+1 WHERE tenant_key=$1 AND id=$2 AND quality_hash=$3`, s.tenant, id, hash)
if err != nil || tag.RowsAffected() != 1 {
return MasterFieldReceipt{}, qaopsquality.ErrConflict
}
if err = tx.Commit(ctx); err != nil {
return MasterFieldReceipt{}, dobotaiops.ErrUnavailable
}
return MasterFieldReceipt{SavedRevision: revision}, nil
}