修复证书部署重试自冲突并补充真实装配回归

This commit is contained in:
xuans 2026-10-01 16:54:53 +08:00
parent f2574e9508
commit 302111c8af
8 changed files with 245 additions and 27 deletions

View File

@ -199,6 +199,10 @@ func NewWithConfigPath(ctx context.Context, cfg config.Config, configPath string
result, err := trackedWorkflow.DeployCurrent(ctx, domainID) result, err := trackedWorkflow.DeployCurrent(ctx, domainID)
return result.Certificate, result.Deployment, err return result.Certificate, result.Deployment, err
}, },
DeployCertificateTask: func(ctx context.Context, domainID string) (certificate.IssueResult, deployment.Report, error) {
result, err := workflow.DeployCurrent(ctx, domainID)
return result.Certificate, result.Deployment, err
},
RestoreCertificateVersion: func(ctx context.Context, domainID, version string) (certificate.IssueResult, error) { RestoreCertificateVersion: func(ctx context.Context, domainID, version string) (certificate.IssueResult, error) {
result, err := trackedWorkflow.Restore(ctx, domainID, version) result, err := trackedWorkflow.Restore(ctx, domainID, version)
return result.Certificate, err return result.Certificate, err

View File

@ -0,0 +1,132 @@
package app
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/certificate"
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
)
func TestApplicationDeployRetryUsesExistingTask(t *testing.T) {
_, application := newCertificateTaskFixture(t)
saved := workflowSavedCertificate(t)
var calls atomic.Int32
started := make(chan struct{})
release := make(chan struct{})
defer close(release)
transactions := make(chan deployment.TransactionContext, 2)
application.Workflow.CurrentCertificate = func(ctx context.Context, id string) (certificate.IssueResult, error) {
transaction, _ := deployment.TransactionContextFromContext(ctx)
transactions <- transaction
if calls.Add(1) == 1 {
return certificate.IssueResult{}, errors.New("首次部署故障")
}
close(started)
select {
case <-release:
case <-ctx.Done():
return certificate.IssueResult{}, ctx.Err()
}
return certificate.IssueResult{DomainID: id, Domains: []string{"example.com"}, Saved: saved}, nil
}
application.Workflow.Servers = workflowServerLister{servers: []deployment.Server{{ID: "server-1"}, {ID: "server-2"}}}
application.Workflow.Deployment = &workflowDeployer{report: deployment.Report{
Total: 2, Success: 2, Status: "success",
Results: []deployment.Result{{ServerID: "server-1", Status: "success"}, {ServerID: "server-2", Status: "success"}},
}}
application.Workflow.Notifier = nil
application.Workflow.RemoteVerifierForServer = func(context.Context, deployment.Server, string, []string) (certificate.ValidationResult, error) {
return certificate.ValidationResult{FingerprintSHA256: saved.FingerprintSHA256}, nil
}
var cookies []*http.Cookie
request := func(path, body string) *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
for _, cookie := range cookies {
req.AddCookie(cookie)
}
response := httptest.NewRecorder()
application.HTTP.Handler().ServeHTTP(response, req)
return response
}
login := request("/api/v1/auth/login", `{"identifier":"admin","password":"Test1234!"}`)
if login.Code != http.StatusOK {
t.Fatalf("真实应用登录失败:%d %s", login.Code, login.Body.String())
}
cookies = login.Result().Cookies()
initial := request("/api/v1/domains/task-domain/deploy", "")
if initial.Code != http.StatusBadGateway || calls.Load() != 1 {
t.Fatalf("首次故障未经过真实部署接线:%d %s", initial.Code, initial.Body.String())
}
tasks, err := application.Tasks.ListTasks(t.Context(), "task-domain", 10)
if err != nil || len(tasks) != 1 || tasks[0].Status != "failed" {
t.Fatalf("首次部署未生成唯一失败任务:%+v %v", tasks, err)
}
task := tasks[0]
retryPath := "/api/v1/tasks/" + task.ID + "/retry"
done := make(chan *httptest.ResponseRecorder, 1)
go func() { done <- request(retryPath, "") }()
select {
case <-started:
case response := <-done:
t.Fatalf("重试未进入原始部署工作流:%d %s", response.Code, response.Body.String())
case <-time.After(5 * time.Second):
t.Fatal("重试工作流启动超时")
}
concurrent := request(retryPath, "")
if concurrent.Code != http.StatusConflict || calls.Load() != 2 {
t.Fatalf("并发重试未被阻止:%d %s", concurrent.Code, concurrent.Body.String())
}
release <- struct{}{}
select {
case response := <-done:
if response.Code != http.StatusOK {
t.Fatalf("真实应用部署重试失败:%d %s", response.Code, response.Body.String())
}
case <-time.After(5 * time.Second):
t.Fatal("重试工作流收尾超时")
}
tasks, err = application.Tasks.ListTasks(t.Context(), "task-domain", 10)
if err != nil || len(tasks) != 1 || tasks[0].ID != task.ID || tasks[0].Status != "success" {
t.Fatalf("重试重复创建任务或未成功:%+v %v", tasks, err)
}
attempts, err := application.Tasks.ListAttempts(t.Context(), task.ID)
if err != nil || len(attempts) != 2 {
t.Fatalf("尝试数量错误:%+v %v", attempts, err)
}
for i, status := range []string{"failed", "success"} {
attempt := attempts[i]
transaction := <-transactions
if attempt.Status != status || attempt.AttemptNumber != i+1 || attempt.FinishedAt == nil ||
transaction.TaskID != task.ID || transaction.AttemptID != attempt.ID || transaction.AttemptNumber != i+1 {
t.Fatalf("重试丢失原任务事务关联:%+v %+v", attempt, transaction)
}
}
repeated := request(retryPath, "")
results, err := application.Tasks.ListTaskDeploymentResults(t.Context(), task.ID)
if err != nil || len(results) != 2 {
t.Fatalf("双服务器结果未保存:%+v %v", results, err)
}
for _, result := range results {
if result.AttemptID != attempts[1].ID || result.AttemptNumber != 2 || result.Status != "success" {
t.Fatalf("服务器结果未关联重试尝试:%+v", result)
}
}
var auditCount int
err = application.Database.DB.QueryRowContext(t.Context(),
"SELECT COUNT(*) FROM audit_logs WHERE task_id = ? AND action = ? AND operator_id = ? AND status IN ('running', 'success')",
task.ID, "certificate_deploy_retry", task.OperatorID).Scan(&auditCount)
if err != nil || auditCount != 2 {
t.Fatalf("重试审计与操作者关联不完整:%d %v", auditCount, err)
}
if repeated.Code != http.StatusConflict || calls.Load() != 2 {
t.Fatalf("成功任务仍被重复执行:%d %s", repeated.Code, repeated.Body.String())
}
}

View File

@ -40,29 +40,31 @@ type readinessSecretReader interface {
} }
type Dependencies struct { type Dependencies struct {
Config config.Config Config config.Config
ConfigPath string ConfigPath string
FrontendDir string FrontendDir string
Logger *logging.Logger Logger *logging.Logger
Repository repository.DomainRepository Repository repository.DomainRepository
Servers repository.ServerRepository Servers repository.ServerRepository
Users repository.UserRepository Users repository.UserRepository
ProjectMembers repository.ProjectMemberRepository ProjectMembers repository.ProjectMemberRepository
ProjectDirectory repository.ProjectDirectoryRepository ProjectDirectory repository.ProjectDirectoryRepository
ProjectLifecycle repository.ProjectLifecycleRepository ProjectLifecycle repository.ProjectLifecycleRepository
ProjectResources repository.ProjectResourceReader ProjectResources repository.ProjectResourceReader
ProjectBindings repository.ProjectBindingRepository ProjectBindings repository.ProjectBindingRepository
ProjectDomains repository.ProjectDomainRepository ProjectDomains repository.ProjectDomainRepository
SSHKeys *sshkey.Service SSHKeys *sshkey.Service
DeploymentFactory deployment.ClientFactory DeploymentFactory deployment.ClientFactory
SetupDNS setupDNSVerifier SetupDNS setupDNSVerifier
Secrets setupSecretWriter Secrets setupSecretWriter
Firewall firewall.Manager Firewall firewall.Manager
IssueCertificate func(context.Context, string) (certificate.IssueResult, deployment.Report, error) IssueCertificate func(context.Context, string) (certificate.IssueResult, deployment.Report, error)
IssueSetupCertificate func(context.Context, certificate.SetupCertificateRequest) (certificate.SavedResource, error) IssueSetupCertificate func(context.Context, certificate.SetupCertificateRequest) (certificate.SavedResource, error)
RollbackSetupCertificate func(context.Context, certificate.SavedResource) error RollbackSetupCertificate func(context.Context, certificate.SavedResource) error
ImportCertificate func(context.Context, certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error) ImportCertificate func(context.Context, certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error)
DeployCertificate func(context.Context, string) (certificate.IssueResult, deployment.Report, error) DeployCertificate func(context.Context, string) (certificate.IssueResult, deployment.Report, error)
// DeployCertificateTask 执行已有任务,不得重复创建任务或尝试记录。
DeployCertificateTask func(context.Context, string) (certificate.IssueResult, deployment.Report, error)
RestoreCertificateVersion func(context.Context, string, string) (certificate.IssueResult, error) RestoreCertificateVersion func(context.Context, string, string) (certificate.IssueResult, error)
ValidateCertificate func(context.Context, string) (certificate.IssueResult, certificate.ValidationResult, error) ValidateCertificate func(context.Context, string) (certificate.IssueResult, certificate.ValidationResult, error)
@ -101,6 +103,7 @@ type Server struct {
rollbackSetupCertificate func(context.Context, certificate.SavedResource) error rollbackSetupCertificate func(context.Context, certificate.SavedResource) error
importCertificate func(context.Context, certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error) importCertificate func(context.Context, certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error)
deployCertificate func(context.Context, string) (certificate.IssueResult, deployment.Report, error) deployCertificate func(context.Context, string) (certificate.IssueResult, deployment.Report, error)
deployCertificateTask func(context.Context, string) (certificate.IssueResult, deployment.Report, error)
restoreVersion func(context.Context, string, string) (certificate.IssueResult, error) restoreVersion func(context.Context, string, string) (certificate.IssueResult, error)
validateCertificateWorkflow func(context.Context, string) (certificate.IssueResult, certificate.ValidationResult, error) validateCertificateWorkflow func(context.Context, string) (certificate.IssueResult, certificate.ValidationResult, error)
@ -165,6 +168,7 @@ func New(deps Dependencies) *Server {
rollbackSetupCertificate: deps.RollbackSetupCertificate, rollbackSetupCertificate: deps.RollbackSetupCertificate,
importCertificate: deps.ImportCertificate, importCertificate: deps.ImportCertificate,
deployCertificate: deps.DeployCertificate, deployCertificate: deps.DeployCertificate,
deployCertificateTask: deps.DeployCertificateTask,
restoreVersion: deps.RestoreCertificateVersion, restoreVersion: deps.RestoreCertificateVersion,
validateCertificateWorkflow: deps.ValidateCertificate, validateCertificateWorkflow: deps.ValidateCertificate,
runRenewal: deps.RunRenewal, runRenewal: deps.RunRenewal,

View File

@ -141,7 +141,7 @@ func (s *Server) executeCertificateIssueTask(c *gin.Context, task repository.Tas
} }
func (s *Server) executeCertificateDeployTask(c *gin.Context, task repository.Task, attempt repository.TaskAttempt, auditAction string) { func (s *Server) executeCertificateDeployTask(c *gin.Context, task repository.Task, attempt repository.TaskAttempt, auditAction string) {
s.executeCertificateTask(c, task, attempt, auditAction, s.deployCertificate, "certificate_deploy", "正在部署证书", "证书部署完成") s.executeCertificateTask(c, task, attempt, auditAction, s.deployCertificateTask, "certificate_deploy", "正在部署证书", "证书部署完成")
} }
func (s *Server) executeCertificateTask(c *gin.Context, task repository.Task, attempt repository.TaskAttempt, auditAction string, operation func(context.Context, string) (certificate.IssueResult, deployment.Report, error), stage, runningMessage, successMessage string) { func (s *Server) executeCertificateTask(c *gin.Context, task repository.Task, attempt repository.TaskAttempt, auditAction string, operation func(context.Context, string) (certificate.IssueResult, deployment.Report, error), stage, runningMessage, successMessage string) {
@ -423,7 +423,7 @@ func (s *Server) retryTask(c *gin.Context) {
} }
var runTask func(*gin.Context, repository.Task, repository.TaskAttempt, string) var runTask func(*gin.Context, repository.Task, repository.TaskAttempt, string)
if item.TaskType == "certificate_deploy" { if item.TaskType == "certificate_deploy" {
if s.deployCertificate == nil { if s.deployCertificateTask == nil {
s.writeError(c, http.StatusNotImplemented, "证书部署接口尚未配置", errors.New("证书部署工作流未配置")) s.writeError(c, http.StatusNotImplemented, "证书部署接口尚未配置", errors.New("证书部署工作流未配置"))
return return
} }

View File

@ -749,7 +749,7 @@ func TestTaskAPIRetriesFailedCertificateDeploymentWithoutDNSCredential(t *testin
return certificate.IssueResult{}, deployment.Report{}, nil return certificate.IssueResult{}, deployment.Report{}, nil
}) })
defer closeDB() defer closeDB()
server.deployCertificate = func(_ context.Context, id string) (certificate.IssueResult, deployment.Report, error) { server.deployCertificateTask = func(_ context.Context, id string) (certificate.IssueResult, deployment.Report, error) {
calls++ calls++
return certificate.IssueResult{DomainID: id}, deployment.Report{Status: "success"}, nil return certificate.IssueResult{DomainID: id}, deployment.Report{Status: "success"}, nil
} }
@ -792,12 +792,42 @@ func TestTaskAPIRetriesFailedCertificateDeploymentWithoutDNSCredential(t *testin
} }
} }
func TestTaskAPIDeployRetryRequiresUntrackedExecutor(t *testing.T) {
server, tasks, _, closeDB := newTaskAPITestServer(t, nil)
defer closeDB()
server.deployCertificate = func(context.Context, string) (certificate.IssueResult, deployment.Report, error) {
t.Fatal("已有任务不得回退到创建新任务的部署入口")
return certificate.IssueResult{}, deployment.Report{}, nil
}
item := repository.Task{
HistoryScope: repository.HistoryScope{ScopeType: repository.ScopeProject, ProjectID: domain.DefaultProjectID},
ID: "deploy-missing-executor", DomainID: "domain-1", TaskType: "certificate_deploy",
Status: "failed", Stage: "certificate_deploy",
}
if err := tasks.CreateTask(t.Context(), item); err != nil {
t.Fatal(err)
}
response := httptest.NewRecorder()
serveAdminHTTP(server, response, httptest.NewRequest(http.MethodPost, "/api/v1/tasks/"+item.ID+"/retry", nil))
if response.Code != http.StatusNotImplemented {
t.Fatalf("缺少已有任务执行器时应拒绝重试:%d %s", response.Code, response.Body.String())
}
stored, err := tasks.GetTask(t.Context(), item.ID)
if err != nil || stored.Status != "failed" {
t.Fatalf("执行器未配置时不应改变任务状态:%+v %v", stored, err)
}
attempts, err := tasks.ListAttempts(t.Context(), item.ID)
if err != nil || len(attempts) != 0 {
t.Fatalf("执行器未配置时不应增加尝试:%+v %v", attempts, err)
}
}
func TestTaskAPIRetriesPartialCertificateDeployment(t *testing.T) { func TestTaskAPIRetriesPartialCertificateDeployment(t *testing.T) {
server, taskRepo, _, closeDB := newTaskAPITestServer(t, func(context.Context, string) (certificate.IssueResult, deployment.Report, error) { server, taskRepo, _, closeDB := newTaskAPITestServer(t, func(context.Context, string) (certificate.IssueResult, deployment.Report, error) {
return certificate.IssueResult{}, deployment.Report{}, nil return certificate.IssueResult{}, deployment.Report{}, nil
}) })
defer closeDB() defer closeDB()
server.deployCertificate = func(_ context.Context, id string) (certificate.IssueResult, deployment.Report, error) { server.deployCertificateTask = func(_ context.Context, id string) (certificate.IssueResult, deployment.Report, error) {
return certificate.IssueResult{DomainID: id}, deployment.Report{Status: "success"}, nil return certificate.IssueResult{DomainID: id}, deployment.Report{Status: "success"}, nil
} }
finished := time.Now().UTC() finished := time.Now().UTC()

View File

@ -1217,3 +1217,19 @@ git diff --check
- [x] 已完成,等待复审:两台服务器交叉探测对方公网 `8443` 均超时;结合实际监听地址确认该内部端口未对外监听,未据此声称其他已有服务端口全部关闭。 - [x] 已完成,等待复审:两台服务器交叉探测对方公网 `8443` 均超时;结合实际监听地址确认该内部端口未对外监听,未据此声称其他已有服务端口全部关闭。
- [!] 边界说明:第一台内部管理服务沿用原有 IP 自签证书,使用测试域名和系统 CA 直连会失败;通过密码 SSH 核查证书及 IP SAN 后,以该证书作为显式信任文件访问回环上游返回 200,未关闭 TLS 校验。这仅是该机内部健康核验,不是第一台管理入口的公网受信 TLS 验收。 - [!] 边界说明:第一台内部管理服务沿用原有 IP 自签证书,使用测试域名和系统 CA 直连会失败;通过密码 SSH 核查证书及 IP SAN 后,以该证书作为显式信任文件访问回环上游返回 200,未关闭 TLS 校验。这仅是该机内部健康核验,不是第一台管理入口的公网受信 TLS 验收。
- [!] 边界说明:本轮保留数据库,没有执行清库首次流程;未额外制作数据备份,官方安装器自身保留升级前发布文件以支持失败回退。完整生产验收仍为 **P0-03:完成,待复审**。 - [!] 边界说明:本轮保留数据库,没有执行清库首次流程;未额外制作数据备份,官方安装器自身保留升级前发布文件以支持失败回退。完整生产验收仍为 **P0-03:完成,待复审**。
### 部署重试阻塞修复进度(2026-10-01 16:51,北京时间)
本节为本轮当前状态说明,前文保留为历史记录。P0-03 的局部代码复审状态不代表整个项目的生产验收状态;本轮上线范围仅 Linux,不将 Windows 列为阻塞。
- [✓] 复审通过:双机服务保持回环监听 127.0.0.1:8443,由 Nginx 80/443 对外提供服务;本轮未新增公网业务端口。
- [x] 已完成,等待复审:通过正式部署接口制造第一台 Nginx 校验命令失败,两台部署报告整体失败,恢复报告为 success;随后已恢复正常校验命令,双机 nginx -t 通过。该验证使用同一证书重部署,不代表不同新旧证书切换验收。
- [x] 已完成,等待复审:定位部署重试自冲突。HTTP 重试已创建尝试并标记原任务运行,但应用装配再次调用创建任务的工作流,将自身判为同类活动任务。新增真实应用登录与路由装配回归,修复前稳定返回 502。
- [x] 已完成,等待复审:拆分普通部署与已有任务执行回调,普通部署继续创建任务,重试只执行原始证书部署流程,保留权限、审计、尝试记录与事务关联。
- [x] 已完成,等待复审:定向回归验证唯一父任务、失败后成功的两次尝试、原任务事务关联、并发重试 409、成功后重复重试 409;后端 go test ./... 与 go vet ./... 通过。
- [~] 进行中:竞态测试、干净提交构建、双机升级和真实失败任务重试复核。当前尚未取得修复后的实机成功证据。
- [!] 暂缓或存在阻塞:历史恢复事务、Smail SMTP、数据库生产矩阵、备份恢复与最终清库首次部署门禁仍未闭环,不宣称生产验收通过,不给缺乏依据的完成百分比。
现场失败任务标识:683ef23a-c912-4f46-b2a6-3ec11908808e。本轮保留其数据用于复现与修复验证,不属于清库首次流程;没有直接改库伪造状态,也没有创建辅助验收脚本。
本轮补充回归:应用装配测试同时核对双服务器结果归属于第二次尝试,以及开始和成功审计的操作者关联;缺少已有任务执行器时返回 501,不回退到创建任务入口,不改变原任务状态。后端全量测试与 vet 再次通过;app/httpapi/repository 竞态测试通过,补充用例的定向竞态测试通过;前端 241 项通过。实机复测尚待新包部署。

View File

@ -1529,3 +1529,19 @@ P0-05 从 `[!]` 调整为 `[~]`,并未标记完成;P0-04 剩余门禁和全
- [x] 已完成,等待复审:两台服务器交叉探测对方公网 `8443` 均超时;结合实际监听地址确认该内部端口未对外监听,未据此声称其他已有服务端口全部关闭。 - [x] 已完成,等待复审:两台服务器交叉探测对方公网 `8443` 均超时;结合实际监听地址确认该内部端口未对外监听,未据此声称其他已有服务端口全部关闭。
- [!] 边界说明:第一台内部管理服务沿用原有 IP 自签证书,使用测试域名和系统 CA 直连会失败;通过密码 SSH 核查证书及 IP SAN 后,以该证书作为显式信任文件访问回环上游返回 200,未关闭 TLS 校验。这仅是该机内部健康核验,不是第一台管理入口的公网受信 TLS 验收。 - [!] 边界说明:第一台内部管理服务沿用原有 IP 自签证书,使用测试域名和系统 CA 直连会失败;通过密码 SSH 核查证书及 IP SAN 后,以该证书作为显式信任文件访问回环上游返回 200,未关闭 TLS 校验。这仅是该机内部健康核验,不是第一台管理入口的公网受信 TLS 验收。
- [!] 边界说明:本轮保留数据库,没有执行清库首次流程;未额外制作数据备份,官方安装器自身保留升级前发布文件以支持失败回退。完整生产验收仍为 **P0-03:完成,待复审**。 - [!] 边界说明:本轮保留数据库,没有执行清库首次流程;未额外制作数据备份,官方安装器自身保留升级前发布文件以支持失败回退。完整生产验收仍为 **P0-03:完成,待复审**。
### 部署重试阻塞修复进度(2026-10-01 16:51,北京时间)
本节为本轮当前状态说明,前文保留为历史记录。P0-03 的局部代码复审状态不代表整个项目的生产验收状态;本轮上线范围仅 Linux,不将 Windows 列为阻塞。
- [✓] 复审通过:双机服务保持回环监听 127.0.0.1:8443,由 Nginx 80/443 对外提供服务;本轮未新增公网业务端口。
- [x] 已完成,等待复审:通过正式部署接口制造第一台 Nginx 校验命令失败,两台部署报告整体失败,恢复报告为 success;随后已恢复正常校验命令,双机 nginx -t 通过。该验证使用同一证书重部署,不代表不同新旧证书切换验收。
- [x] 已完成,等待复审:定位部署重试自冲突。HTTP 重试已创建尝试并标记原任务运行,但应用装配再次调用创建任务的工作流,将自身判为同类活动任务。新增真实应用登录与路由装配回归,修复前稳定返回 502。
- [x] 已完成,等待复审:拆分普通部署与已有任务执行回调,普通部署继续创建任务,重试只执行原始证书部署流程,保留权限、审计、尝试记录与事务关联。
- [x] 已完成,等待复审:定向回归验证唯一父任务、失败后成功的两次尝试、原任务事务关联、并发重试 409、成功后重复重试 409;后端 go test ./... 与 go vet ./... 通过。
- [~] 进行中:竞态测试、干净提交构建、双机升级和真实失败任务重试复核。当前尚未取得修复后的实机成功证据。
- [!] 暂缓或存在阻塞:历史恢复事务、Smail SMTP、数据库生产矩阵、备份恢复与最终清库首次部署门禁仍未闭环,不宣称生产验收通过,不给缺乏依据的完成百分比。
现场失败任务标识:683ef23a-c912-4f46-b2a6-3ec11908808e。本轮保留其数据用于复现与修复验证,不属于清库首次流程;没有直接改库伪造状态,也没有创建辅助验收脚本。
本轮补充回归:应用装配测试同时核对双服务器结果归属于第二次尝试,以及开始和成功审计的操作者关联;缺少已有任务执行器时返回 501,不回退到创建任务入口,不改变原任务状态。后端全量测试与 vet 再次通过;app/httpapi/repository 竞态测试通过,补充用例的定向竞态测试通过;前端 241 项通过。实机复测尚待新包部署。

View File

@ -2159,3 +2159,19 @@ P0-11 生产验收门禁
- [x] 已完成,等待复审:两台服务器交叉探测对方公网 `8443` 均超时;结合实际监听地址确认该内部端口未对外监听,未据此声称其他已有服务端口全部关闭。 - [x] 已完成,等待复审:两台服务器交叉探测对方公网 `8443` 均超时;结合实际监听地址确认该内部端口未对外监听,未据此声称其他已有服务端口全部关闭。
- [!] 边界说明:第一台内部管理服务沿用原有 IP 自签证书,使用测试域名和系统 CA 直连会失败;通过密码 SSH 核查证书及 IP SAN 后,以该证书作为显式信任文件访问回环上游返回 200,未关闭 TLS 校验。这仅是该机内部健康核验,不是第一台管理入口的公网受信 TLS 验收。 - [!] 边界说明:第一台内部管理服务沿用原有 IP 自签证书,使用测试域名和系统 CA 直连会失败;通过密码 SSH 核查证书及 IP SAN 后,以该证书作为显式信任文件访问回环上游返回 200,未关闭 TLS 校验。这仅是该机内部健康核验,不是第一台管理入口的公网受信 TLS 验收。
- [!] 边界说明:本轮保留数据库,没有执行清库首次流程;未额外制作数据备份,官方安装器自身保留升级前发布文件以支持失败回退。完整生产验收仍为 **P0-03:完成,待复审**。 - [!] 边界说明:本轮保留数据库,没有执行清库首次流程;未额外制作数据备份,官方安装器自身保留升级前发布文件以支持失败回退。完整生产验收仍为 **P0-03:完成,待复审**。
### 部署重试阻塞修复进度(2026-10-01 16:51,北京时间)
本节为本轮当前状态说明,前文保留为历史记录。P0-03 的局部代码复审状态不代表整个项目的生产验收状态;本轮上线范围仅 Linux,不将 Windows 列为阻塞。
- [✓] 复审通过:双机服务保持回环监听 127.0.0.1:8443,由 Nginx 80/443 对外提供服务;本轮未新增公网业务端口。
- [x] 已完成,等待复审:通过正式部署接口制造第一台 Nginx 校验命令失败,两台部署报告整体失败,恢复报告为 success;随后已恢复正常校验命令,双机 nginx -t 通过。该验证使用同一证书重部署,不代表不同新旧证书切换验收。
- [x] 已完成,等待复审:定位部署重试自冲突。HTTP 重试已创建尝试并标记原任务运行,但应用装配再次调用创建任务的工作流,将自身判为同类活动任务。新增真实应用登录与路由装配回归,修复前稳定返回 502。
- [x] 已完成,等待复审:拆分普通部署与已有任务执行回调,普通部署继续创建任务,重试只执行原始证书部署流程,保留权限、审计、尝试记录与事务关联。
- [x] 已完成,等待复审:定向回归验证唯一父任务、失败后成功的两次尝试、原任务事务关联、并发重试 409、成功后重复重试 409;后端 go test ./... 与 go vet ./... 通过。
- [~] 进行中:竞态测试、干净提交构建、双机升级和真实失败任务重试复核。当前尚未取得修复后的实机成功证据。
- [!] 暂缓或存在阻塞:历史恢复事务、Smail SMTP、数据库生产矩阵、备份恢复与最终清库首次部署门禁仍未闭环,不宣称生产验收通过,不给缺乏依据的完成百分比。
现场失败任务标识:683ef23a-c912-4f46-b2a6-3ec11908808e。本轮保留其数据用于复现与修复验证,不属于清库首次流程;没有直接改库伪造状态,也没有创建辅助验收脚本。
本轮补充回归:应用装配测试同时核对双服务器结果归属于第二次尝试,以及开始和成功审计的操作者关联;缺少已有任务执行器时返回 501,不回退到创建任务入口,不改变原任务状态。后端全量测试与 vet 再次通过;app/httpapi/repository 竞态测试通过,补充用例的定向竞态测试通过;前端 241 项通过。实机复测尚待新包部署。