package qaopsquality import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "github.com/jackc/pgx/v5" "knowledge-governance/internal/dobotaiops" "knowledge-governance/internal/qaopsfields" "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 MasterFieldNoncePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.:-]{15,127}$`) func ValidMasterFieldID(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 == nil || s.pool == nil { return MasterFieldReceipt{}, dobotaiops.ErrUnavailable } 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()) receipt, err := s.PatchMasterFieldsInTx(ctx, tx, id, q, actor) if err != nil { return MasterFieldReceipt{}, err } if tx.Commit(ctx) != nil { return MasterFieldReceipt{}, dobotaiops.ErrUnavailable } return receipt, nil } func (s *Store) PatchMasterFieldsInTx(ctx context.Context, tx pgx.Tx, id string, q MasterFieldPatch, actor string) (MasterFieldReceipt, error) { if s == nil || s.pool == nil || !ValidMasterFieldID(s.tenant) { return MasterFieldReceipt{}, dobotaiops.ErrUnavailable } if !ValidMasterFieldID(id) || !ValidMasterFieldID(actor) || !inlineHash.MatchString(q.InputHash) || !MasterFieldNoncePattern.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 } } var err error 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{}, 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{}, 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(¤tRevision, &previousHash, &previousReview, &raw) if err != nil && !errors.Is(err, pgx.ErrNoRows) { return MasterFieldReceipt{}, dobotaiops.ErrUnavailable } if currentRevision != q.BaseRevision { return MasterFieldReceipt{}, 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{}, 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 } views, err := NewStore(s.pool, s.tenant+"::master").ReadViewsInTx(ctx, tx, []dobotaiops.QualityKey{{ID: id, InputHash: masterHash}}) if err != nil { return MasterFieldReceipt{}, err } current := 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 } coverageValue := beforeAll["coverage"] if value, changed := q.Fields["coverage"]; changed { coverageValue = value } _, correctnessChanged := q.Fields["is_correct"] if correctnessChanged && ((correctness == "待确认") != (coverageValue == "否")) { return MasterFieldReceipt{}, dobotaiops.ErrInvalidQuery } if correctnessChanged && coverageValue != "否" && correctness != "否" && FeedbackForcesIncorrect(feedback) { return MasterFieldReceipt{}, ErrDownvoted } 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{}, ErrConflict } return MasterFieldReceipt{SavedRevision: revision}, nil }