013ce4a24e
build / build (push) Successful in 1m3s
收录 main、注释开发快照和完整运行 SHA;开发说明绑定原提交,补记 0.1.1–0.1.25。 进行中同步按仓库合并,完成后重新执行;启动入队,部署脚本读回版本证据。 开发快照独立于正式标签与 Release,既有冲突标签不移动。 已运行:整仓 make verify,Go 673 通过、前端 274 通过,92 个已声明可选跳过,必需跳过 0。 隔离 PostgreSQL 重复同步队列用例及 -race 通过;未更新现有 8088、未修改生产问答。
64 lines
2.5 KiB
Go
64 lines
2.5 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"github.com/jackc/pgx/v5"
|
|
"knowledge-governance/internal/jobs"
|
|
"knowledge-governance/internal/versionhistory"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func (s *Store) EnqueueVersionHistorySync(ctx context.Context, repo, actor, target string, now time.Time) (*jobs.Job, error) {
|
|
if strings.Count(repo, "/") != 1 || strings.TrimSpace(repo) != repo || len(repo) > 200 || actor == "" || len(actor) > 256 || target != "" && !regexp.MustCompile(`^[a-f0-9]{40}$`).MatchString(target) {
|
|
return nil, errors.New("invalid version synchronization request")
|
|
}
|
|
tx, e := s.db.Begin(ctx)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
defer tx.Rollback(context.Background())
|
|
if _, e = tx.Exec(ctx, `SELECT pg_advisory_xact_lock(hashtextextended($1,0))`, "version-history:"+repo); e != nil {
|
|
return nil, e
|
|
}
|
|
// Retire exhausted expired owners. Terminal jobs are history, not a perpetual
|
|
// idempotency key: a later refresh must be allowed to execute again.
|
|
if _, e = tx.Exec(ctx, `UPDATE jobs SET state='failed',lease_owner=NULL,lease_until=NULL,updated_at=$3 WHERE type=$1 AND payload->>'repo_key'=$2 AND state='running' AND lease_until<clock_timestamp() AND attempt>=max_attempts`, versionhistory.JobType, repo, now); e != nil {
|
|
return nil, e
|
|
}
|
|
var active string
|
|
e = tx.QueryRow(ctx, `SELECT id FROM jobs WHERE type=$1 AND payload->>'repo_key'=$2 AND state IN ('pending','running','retry') ORDER BY created_at DESC,id DESC LIMIT 1`, versionhistory.JobType, repo).Scan(&active)
|
|
if e != nil && !errors.Is(e, pgx.ErrNoRows) {
|
|
return nil, e
|
|
}
|
|
if active != "" {
|
|
job, e := scanJob(tx.QueryRow(ctx, `SELECT id,type,payload,state,priority,available_at,lease_owner,lease_until,attempt,max_attempts,last_error,idempotency_key,created_at,updated_at FROM jobs WHERE id=$1`, active))
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if e = tx.Commit(ctx); e != nil {
|
|
return nil, e
|
|
}
|
|
return job, nil
|
|
}
|
|
var random [16]byte
|
|
if _, e = rand.Read(random[:]); e != nil {
|
|
return nil, e
|
|
}
|
|
id := "vh-" + hex.EncodeToString(random[:])
|
|
payload, _ := json.Marshal(map[string]string{"repo_key": repo, "triggered_by": actor, "target_sha": target})
|
|
job, e := NewStore(tx).Enqueue(ctx, jobs.EnqueueRequest{ID: id, Type: versionhistory.JobType, Payload: payload, AvailableAt: now, MaxAttempts: 3, IdempotencyKey: "version-history:" + repo + ":" + id}, now)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if e = tx.Commit(ctx); e != nil {
|
|
return nil, e
|
|
}
|
|
return job, nil
|
|
}
|