修复本机核查长路径传输并记录竞态与静态检查进度
This commit is contained in:
parent
75b3b719cc
commit
6b77730114
@ -37,7 +37,7 @@ func Begin(ctx context.Context, conn net.Conn) (*Client, error) {
|
||||
client.stop = context.AfterFunc(ctx, func() { _ = conn.Close() })
|
||||
var response Response
|
||||
if err := readFrame(client.reader, &response); err != nil || !validChallenge(response) ||
|
||||
len(response.Methods) == 0 || len(response.Methods) > 8 || response.RecoveryPage != nil || response.Inspection != nil {
|
||||
len(response.Methods) == 0 || len(response.Methods) > 8 || response.RecoveryPage != nil || response.Inspection != nil || response.InspectionPart != nil {
|
||||
_ = client.Close()
|
||||
return nil, ErrProtocol
|
||||
}
|
||||
@ -77,7 +77,7 @@ func (c *Client) exchange(request Request, stage int) error {
|
||||
_ = c.Close()
|
||||
return ErrProtocol
|
||||
}
|
||||
if response.RecoveryAvailable || response.RecoveryPage != nil || response.Inspection != nil {
|
||||
if response.RecoveryAvailable || response.RecoveryPage != nil || response.Inspection != nil || response.InspectionPart != nil {
|
||||
c.stage = -1
|
||||
_ = c.Close()
|
||||
return ErrProtocol
|
||||
|
||||
@ -59,6 +59,7 @@ type Response struct {
|
||||
RecoveryAvailable bool `json:"recovery_available,omitempty"`
|
||||
RecoveryPage *deployment.PendingRecoveryPage `json:"recovery_page,omitempty"`
|
||||
Inspection *deployment.RecoveryInspection `json:"inspection,omitempty"`
|
||||
InspectionPart *inspectionPart `json:"inspection_part,omitempty"`
|
||||
}
|
||||
|
||||
// Session 由主服务创建,身份验证及密码持久化仍由主服务负责。
|
||||
|
||||
@ -81,7 +81,7 @@ func (c *Client) recoveryExchange(request Request) (Response, error) {
|
||||
return fail()
|
||||
}
|
||||
if request.Action == "recovery_list" {
|
||||
if response.RecoveryPage == nil || response.Inspection != nil || len(response.RecoveryPage.Items) > deployment.RecoveryPageSize {
|
||||
if response.RecoveryPage == nil || response.Inspection != nil || response.InspectionPart != nil || len(response.RecoveryPage.Items) > deployment.RecoveryPageSize {
|
||||
return fail()
|
||||
}
|
||||
previous := request.Cursor
|
||||
@ -95,9 +95,17 @@ func (c *Client) recoveryExchange(request Request) (Response, error) {
|
||||
(len(response.RecoveryPage.Items) != deployment.RecoveryPageSize || cursor != previous) {
|
||||
return fail()
|
||||
}
|
||||
} else if response.Inspection == nil || response.RecoveryPage != nil || response.Inspection.TransactionID != request.TransactionID ||
|
||||
len(response.Inspection.Files) != 9 || len(response.Inspection.Locks) != 3 || response.Inspection.ObservedAt.IsZero() {
|
||||
return fail()
|
||||
} else {
|
||||
if response.InspectionPart != nil {
|
||||
inspection, err := readInspectionParts(c.reader, response)
|
||||
if err != nil {
|
||||
return fail()
|
||||
}
|
||||
response.Inspection, response.InspectionPart = inspection, nil
|
||||
}
|
||||
if !validInspectionPayload(response.Inspection) || response.RecoveryPage != nil || response.Inspection.TransactionID != request.TransactionID {
|
||||
return fail()
|
||||
}
|
||||
}
|
||||
c.nonce, c.stage = response.Nonce, 3
|
||||
return response, nil
|
||||
|
||||
116
backend/internal/localreset/recovery_frames.go
Normal file
116
backend/internal/localreset/recovery_frames.go
Normal file
@ -0,0 +1,116 @@
|
||||
package localreset
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
|
||||
)
|
||||
|
||||
const (
|
||||
inspectionPartSize = 8 << 10
|
||||
maxInspectionParts = 48
|
||||
maxInspectionBytes = inspectionPartSize * maxInspectionParts
|
||||
maxInspectionPathBytes = 4096 + len(".scert-backup-") + 160
|
||||
)
|
||||
|
||||
// 单帧仍限制为 16 KiB;总量覆盖十二条最长路径经 JSON 六倍转义后的大小。
|
||||
// 分片不增加业务请求次数,不重复采样;任何缺片、乱序或超限均不返回部分核查结果。
|
||||
type inspectionPart struct {
|
||||
Index int `json:"index"`
|
||||
Total int `json:"total"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
func validInspectionPayload(value *deployment.RecoveryInspection) bool {
|
||||
if value == nil || !validRecoveryID(value.TransactionID) || len(value.Files) != 9 || len(value.Locks) != 3 || value.ObservedAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
for _, file := range value.Files {
|
||||
if len(file.Path) > maxInspectionPathBytes {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, lock := range value.Locks {
|
||||
if len(lock.Path) > maxInspectionPathBytes {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func writeRecoveryResponse(ctx context.Context, writer io.Writer, response Response) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if response.Inspection == nil {
|
||||
return writeFrame(writer, response)
|
||||
}
|
||||
if !validInspectionPayload(response.Inspection) || response.InspectionPart != nil || response.RecoveryPage != nil {
|
||||
return ErrProtocol
|
||||
}
|
||||
encoded, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return ErrProtocol
|
||||
}
|
||||
if len(encoded)+1 <= MaxFrameSize {
|
||||
return writeFrame(writer, response)
|
||||
}
|
||||
data, err := json.Marshal(response.Inspection)
|
||||
if err != nil || len(data) > maxInspectionBytes {
|
||||
return ErrProtocol
|
||||
}
|
||||
total := (len(data) + inspectionPartSize - 1) / inspectionPartSize
|
||||
response.Inspection = nil
|
||||
for index := 0; index < total; index++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
start := index * inspectionPartSize
|
||||
end := min(start+inspectionPartSize, len(data))
|
||||
response.InspectionPart = &inspectionPart{Index: index, Total: total, Data: data[start:end]}
|
||||
if err := writeFrame(writer, response); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readInspectionParts(reader *bufio.Reader, first Response) (*deployment.RecoveryInspection, error) {
|
||||
if first.InspectionPart == nil || first.InspectionPart.Total < 1 || first.InspectionPart.Total > maxInspectionParts {
|
||||
return nil, ErrProtocol
|
||||
}
|
||||
total := first.InspectionPart.Total
|
||||
data := make([]byte, 0, total*inspectionPartSize)
|
||||
response := first
|
||||
for index := 0; index < total; index++ {
|
||||
if index > 0 {
|
||||
response = Response{}
|
||||
if err := readFrame(reader, &response); err != nil {
|
||||
return nil, ErrProtocol
|
||||
}
|
||||
}
|
||||
part := response.InspectionPart
|
||||
if !validChallenge(response) || response.Nonce != first.Nonce || len(response.Methods) != 0 || response.RecoveryAvailable ||
|
||||
response.Inspection != nil || response.RecoveryPage != nil || part == nil || part.Index != index || part.Total != total ||
|
||||
len(part.Data) == 0 || len(part.Data) > inspectionPartSize || (index < total-1 && len(part.Data) != inspectionPartSize) {
|
||||
return nil, ErrProtocol
|
||||
}
|
||||
data = append(data, part.Data...)
|
||||
}
|
||||
var inspection deployment.RecoveryInspection
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
if decoder.Decode(&inspection) != nil || !validInspectionPayload(&inspection) {
|
||||
return nil, ErrProtocol
|
||||
}
|
||||
var extra any
|
||||
if !errors.Is(decoder.Decode(&extra), io.EOF) {
|
||||
return nil, ErrProtocol
|
||||
}
|
||||
return &inspection, nil
|
||||
}
|
||||
215
backend/internal/localreset/recovery_frames_test.go
Normal file
215
backend/internal/localreset/recovery_frames_test.go
Normal file
@ -0,0 +1,215 @@
|
||||
package localreset
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
|
||||
)
|
||||
|
||||
func longInspectionFixture() deployment.RecoveryInspection {
|
||||
value := deployment.RecoveryInspection{
|
||||
TransactionID: strings.Repeat("t", 160), ServerID: "server-1",
|
||||
Phase: "recovery_required", Status: "recovery_required", ObservedAt: time.Now().UTC(),
|
||||
}
|
||||
for _, name := range []string{"certificate", "chain", "key"} {
|
||||
path := "/" + strings.Repeat(strings.Repeat("<", 199)+"/", 20)
|
||||
path += strings.Repeat("&", 4096-len(path)-len(name)) + name
|
||||
for _, suffix := range []string{"", ".scert-tmp-" + value.TransactionID, ".scert-backup-" + value.TransactionID} {
|
||||
value.Files = append(value.Files, deployment.RecoveryFileObservation{
|
||||
Path: path + suffix, Kind: "target", Exists: true, Digest: strings.Repeat("a", 64), Comparison: "unproven",
|
||||
})
|
||||
}
|
||||
value.Locks = append(value.Locks, deployment.RecoveryLockObservation{Path: path + ".scert-lock", Exists: true, Ownership: "unproven"})
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func TestRecoveryInspectionLongPathsRoundTrip(t *testing.T) {
|
||||
inspection := longInspectionFixture()
|
||||
encoded, err := json.Marshal(inspection)
|
||||
if err != nil || len(encoded) <= 250<<10 || len(encoded) > maxInspectionBytes {
|
||||
t.Fatalf("fixture does not exercise escaped path bound: %d %v", len(encoded), err)
|
||||
}
|
||||
session := &recoveryTestSession{inspection: &inspection}
|
||||
_, conn, _ := recoveryConnection(t, session)
|
||||
client, err := Begin(t.Context(), conn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
before := client.nonce
|
||||
result, err := client.InspectRecovery(inspection.TransactionID)
|
||||
if err != nil || !reflect.DeepEqual(result, inspection) {
|
||||
t.Fatalf("long observation changed or failed: %v", err)
|
||||
}
|
||||
if client.nonce == before || session.inspects.Load() != 1 {
|
||||
t.Fatal("nonce unchanged or observation repeated")
|
||||
}
|
||||
if _, err := client.ListRecoveries(""); err != nil || session.lists.Load() != 1 {
|
||||
t.Fatalf("next request failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecoveryInspectionFrameBounds(t *testing.T) {
|
||||
inspection := longInspectionFixture()
|
||||
response := Response{Version: Version, OK: true, Code: "CHALLENGE", Nonce: strings.Repeat("a", 64), Inspection: &inspection}
|
||||
var output bytes.Buffer
|
||||
if err := writeRecoveryResponse(t.Context(), &output, response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lines := bytes.Split(bytes.TrimSuffix(output.Bytes(), []byte{'\n'}), []byte{'\n'})
|
||||
if len(lines) < 2 || len(lines) > maxInspectionParts {
|
||||
t.Fatalf("unexpected fragment count: %d", len(lines))
|
||||
}
|
||||
for index, line := range lines {
|
||||
var frame Response
|
||||
if len(line)+1 > MaxFrameSize || json.Unmarshal(line, &frame) != nil || frame.InspectionPart == nil ||
|
||||
frame.InspectionPart.Index != index || frame.InspectionPart.Total != len(lines) || frame.Nonce != response.Nonce {
|
||||
t.Fatal("frame exceeds limit or has invalid sequence")
|
||||
}
|
||||
}
|
||||
inspection.Status = strings.Repeat("x", maxInspectionBytes)
|
||||
output.Reset()
|
||||
if err := writeRecoveryResponse(t.Context(), &output, response); !errors.Is(err, ErrProtocol) || output.Len() != 0 {
|
||||
t.Fatal("oversized observation emitted partial frames")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecoveryInspectionRejectsMalformedParts(t *testing.T) {
|
||||
for _, scenario := range []string{"first-index", "many-parts", "zero-total", "negative-total", "large-part", "short-part", "missing-last", "index", "total", "nonce", "mixed", "methods", "capability", "empty-part", "missing-part", "status", "unknown-field", "trailing-json", "partial-observation", "wrong-id"} {
|
||||
t.Run(scenario, func(t *testing.T) {
|
||||
serverConn, conn := net.Pipe()
|
||||
defer serverConn.Close()
|
||||
defer conn.Close()
|
||||
_ = serverConn.SetDeadline(time.Now().Add(2 * time.Second))
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer serverConn.Close()
|
||||
nonce := strings.Repeat("a", 64)
|
||||
if writeFrame(serverConn, Response{Version: Version, OK: true, Code: "CHALLENGE", Nonce: nonce,
|
||||
Methods: []Method{{ID: "email"}}, RecoveryAvailable: true}) != nil {
|
||||
return
|
||||
}
|
||||
var request Request
|
||||
if readFrame(bufio.NewReaderSize(serverConn, MaxFrameSize+1), &request) != nil {
|
||||
return
|
||||
}
|
||||
inspection := longInspectionFixture()
|
||||
if scenario == "partial-observation" {
|
||||
inspection.Files = inspection.Files[:8]
|
||||
} else if scenario == "wrong-id" {
|
||||
inspection.TransactionID = "other"
|
||||
}
|
||||
data, _ := json.Marshal(inspection)
|
||||
if scenario == "unknown-field" {
|
||||
data = append(data[:len(data)-1], []byte(`,"unexpected":true}`)...)
|
||||
} else if scenario == "trailing-json" {
|
||||
data = append(data, []byte(" {}")...)
|
||||
}
|
||||
total := (len(data) + inspectionPartSize - 1) / inspectionPartSize
|
||||
for index := 0; index < total; index++ {
|
||||
if scenario == "missing-last" && index == total-1 {
|
||||
return
|
||||
}
|
||||
part := &inspectionPart{Index: index, Total: total, Data: data[index*inspectionPartSize : min((index+1)*inspectionPartSize, len(data))]}
|
||||
response := Response{Version: Version, OK: true, Code: "CHALLENGE", Nonce: strings.Repeat("b", 64), InspectionPart: part}
|
||||
if index == 0 {
|
||||
switch scenario {
|
||||
case "first-index":
|
||||
part.Index = 1
|
||||
case "many-parts":
|
||||
part.Total = maxInspectionParts + 1
|
||||
case "zero-total":
|
||||
part.Total = 0
|
||||
case "negative-total":
|
||||
part.Total = -1
|
||||
case "large-part":
|
||||
part.Data = make([]byte, inspectionPartSize+1)
|
||||
case "short-part":
|
||||
part.Data = part.Data[:100]
|
||||
}
|
||||
}
|
||||
if index == 1 {
|
||||
switch scenario {
|
||||
case "index":
|
||||
part.Index = 0
|
||||
case "total":
|
||||
part.Total++
|
||||
case "nonce":
|
||||
response.Nonce = strings.Repeat("c", 64)
|
||||
case "mixed":
|
||||
response.RecoveryPage = &deployment.PendingRecoveryPage{}
|
||||
case "methods":
|
||||
response.Methods = []Method{{ID: "email"}}
|
||||
case "capability":
|
||||
response.RecoveryAvailable = true
|
||||
case "empty-part":
|
||||
part.Data = nil
|
||||
case "missing-part":
|
||||
response.InspectionPart = nil
|
||||
case "status":
|
||||
response.OK = false
|
||||
}
|
||||
}
|
||||
if writeFrame(serverConn, response) != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
client, err := Begin(t.Context(), conn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
result, err := client.InspectRecovery(strings.Repeat("t", 160))
|
||||
if err == nil || result.TransactionID != "" || len(result.Files) != 0 || client.stage != -1 {
|
||||
t.Fatal("invalid fragments returned partial observation or reusable connection")
|
||||
}
|
||||
<-done
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type cancelInspectionWriter struct {
|
||||
output bytes.Buffer
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (w *cancelInspectionWriter) Write(data []byte) (int, error) {
|
||||
n, err := w.output.Write(data)
|
||||
w.cancel()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func TestRecoveryInspectionCancellationStopsFragments(t *testing.T) {
|
||||
inspection := longInspectionFixture()
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
writer := &cancelInspectionWriter{cancel: cancel}
|
||||
err := writeRecoveryResponse(ctx, writer, Response{Version: Version, OK: true, Code: "CHALLENGE", Nonce: strings.Repeat("b", 64), Inspection: &inspection})
|
||||
if !errors.Is(err, context.Canceled) || bytes.Count(writer.output.Bytes(), []byte{'\n'}) != 1 {
|
||||
t.Fatalf("cancel did not stop after first frame: %v", err)
|
||||
}
|
||||
reader := bufio.NewReaderSize(&writer.output, MaxFrameSize+1)
|
||||
var first Response
|
||||
if readFrame(reader, &first) != nil {
|
||||
t.Fatal("first frame unreadable")
|
||||
}
|
||||
if result, err := readInspectionParts(reader, first); err == nil || result != nil {
|
||||
t.Fatal("canceled fragments accepted")
|
||||
}
|
||||
if err := writeRecoveryResponse(ctx, io.Discard, Response{Inspection: &inspection}); !errors.Is(err, context.Canceled) {
|
||||
t.Fatal("pre-canceled operation accepted")
|
||||
}
|
||||
}
|
||||
@ -33,7 +33,7 @@ func (s *Server) handleRecovery(ctx context.Context, conn net.Conn, reader *bufi
|
||||
_ = writeFrame(conn, Response{Version: Version, Code: "REJECTED"})
|
||||
return
|
||||
}
|
||||
if writeFrame(conn, response) != nil {
|
||||
if writeRecoveryResponse(ctx, conn, response) != nil {
|
||||
return
|
||||
}
|
||||
request = Request{}
|
||||
|
||||
@ -15,10 +15,11 @@ import (
|
||||
|
||||
type recoveryTestSession struct {
|
||||
testSession
|
||||
lists atomic.Int32
|
||||
inspects atomic.Int32
|
||||
fail bool
|
||||
oversized bool
|
||||
lists atomic.Int32
|
||||
inspects atomic.Int32
|
||||
fail bool
|
||||
oversized bool
|
||||
inspection *deployment.RecoveryInspection
|
||||
}
|
||||
|
||||
func (*recoveryTestSession) RecoveryAvailable() bool { return true }
|
||||
@ -28,6 +29,9 @@ func (s *recoveryTestSession) ListRecoveries(context.Context, string) (deploymen
|
||||
}
|
||||
func (s *recoveryTestSession) InspectRecovery(_ context.Context, id string) (deployment.RecoveryInspection, error) {
|
||||
s.inspects.Add(1)
|
||||
if s.inspection != nil {
|
||||
return *s.inspection, nil
|
||||
}
|
||||
result := deployment.RecoveryInspection{TransactionID: id, ServerID: "s1", ObservedAt: time.Now(),
|
||||
Files: make([]deployment.RecoveryFileObservation, 9), Locks: make([]deployment.RecoveryLockObservation, 3)}
|
||||
if s.fail {
|
||||
|
||||
@ -1,5 +1,17 @@
|
||||
# Linux 生产流程测试记录
|
||||
|
||||
## 2026年10月3日19:02 本机核查边界回归
|
||||
|
||||
第一台 `8.137.23.95` 隔离目录 `/var/tmp/scert-local-recovery-20261003-1828/`;不使用第二台运行本轮回归,不清理业务数据库或恢复现场。
|
||||
|
||||
- [x] `full.log`:数据库修复后的后端全量退出码零,耗时四分四十四秒。外部数据库及实机可选测试缺少环境配置时跳过,不属于生产验收证据。
|
||||
- [!] `race.log`:数据库、部署、本机协议、管理员终端四包通过,HTTP 依赖编译因内存限流达到二十分钟上限;总退出码一、单元 Result=timeout、MainPID=0,未当成测试通过。
|
||||
- [x] `frames-vet.log`:长路径有界分片版本的本机协议、管理员终端和部署三包竞态通过;SQLite 截止时间两组专项重复二十次通过;后端完整 `go vet -p 1 ./...` 通过;联合命令退出码零,耗时一分四十八秒。
|
||||
- [~] `final-full-http-race.log`:最新全量与 HTTP 单包竞态串行运行,结果待记录。隔离任务内存硬上限 900 MiB、CPU 75%,未调整生产服务配置或关闭校验。
|
||||
- [x] 第二台磁盘再次复核:根分区 29 GiB、已用 2.9 GiB、可用 26 GiB,使用率 10%,inode 3%;五项原业务服务 active,Nginx 配置检查通过。本轮没有扩大删除范围。
|
||||
- [ ] 新代码尚未通过正式安装器部署;三库首次 Web、系统发信、DNS/ACME、公共 HTTPS、证书推送与回滚仍需独立实测。
|
||||
|
||||
|
||||
## 2026年10月3日18:28 最新执行证据
|
||||
|
||||
本节优先于下方历史状态;第一台部署测试,第二台仅推送测试。源码隔离测试不替代清库后的正式安装与浏览器业务验收,未清理生产数据库或部署现场。
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
# Scert 开发任务清单
|
||||
|
||||
## 2026年10月3日19:02 当前开发进度
|
||||
|
||||
本节优先于历史章节。第一台用于部署,第二台仅用于推送;仅处理 Linux。
|
||||
|
||||
- [x] SQLite 截止时间边界已修复,修复后全量通过;专项二十次重复通过。
|
||||
- [x] R2 长路径核查响应有界分片已实现,单帧限制不变;三包竞态和后端完整静态检查通过,等待正式包复审。
|
||||
- [!] 原 HTTP 竞态依赖编译超时,不能记为完成;最新全量和 HTTP 竞态正在串行验证。
|
||||
- [~] R3、R4 继续依据《部署事务人工处置设计》推进,独立记录、确认和持久化互斥尚未实现。
|
||||
- [ ] R5 正式候选包与真实生产流程未完成;不因只读功能通过而关闭整体门禁。
|
||||
|
||||
|
||||
## 2026年10月3日18:28 当前开发入口
|
||||
|
||||
本节优先于下方历史状态。最新确认范围:Linux;第一台部署 Scert,第二台仅作证书推送目标,Smail 使用 `web.cdis.cn`。
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
# Scert 整改任务执行清单
|
||||
|
||||
## 2026年10月3日19:02 当前执行状态
|
||||
|
||||
本节优先于历史章节。第一台部署、第二台推送,Linux 范围不变。
|
||||
|
||||
- [x] 数据库截止时间修复后的原生后端全量通过,相关专项重复二十次通过。
|
||||
- [x] R2 有界分片实现及异常传输回归通过;本机协议、管理员终端、部署三包竞态通过;完整 `go vet -p 1 ./...` 通过。
|
||||
- [!] 原五包竞态任务因 HTTP 依赖编译限流超时,整体失败,四包成功不得替代整体结论。
|
||||
- [~] 最新全量和 HTTP 竞态串行验证中;R3、R4 人工处置闭环继续开发。
|
||||
- [ ] 新版正式候选包、三库首次 Web、真实邮件、DNS/ACME、严格 HTTPS 和回滚恢复仍待完成。
|
||||
|
||||
|
||||
## 2026年10月3日18:28 当前执行入口
|
||||
|
||||
本节优先于下方历史状态。第一台 `8.137.23.95` 用于部署测试,第二台 `45.136.13.241` 仅用于证书推送测试;不增加双机主服务要求,不处理 Windows。
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
# Scert 整改、合并与优化清单
|
||||
|
||||
## 2026年10月3日19:02 最新进度
|
||||
|
||||
本节为最新状态,后续章节仅保留历史过程。第一台用于部署测试,第二台仅用于推送测试,Smail 为 `web.cdis.cn`,只处理 Linux。
|
||||
|
||||
- [x] SQLite 截止时间修复后的原生 Linux 后端全量通过;截止时间确定性用例及执行路径专项重复二十次通过。
|
||||
- [x] R2 长路径响应改为有界分片:单帧仍为 16 KiB,数据片 8 KiB,最多四十八片;不截断、不重复采样,不返回部分结果。长路径转义、缺片、乱序、令牌变化、超限及取消回归通过。
|
||||
- [x] 最新本机协议、管理员终端、部署三包竞态及后端完整静态检查通过,等待最新全量与正式包复审。
|
||||
- [!] 旧五包竞态任务四包通过,HTTP 包依赖编译因资源限流达到二十分钟上限;整体退出码一,不能记为通过。
|
||||
- [~] 最新后端全量与 HTTP 单包竞态串行运行;仅调整隔离测试资源限额,未修改业务服务。
|
||||
- [ ] R3、R4 人工处置、独立审计与持久化互斥仍未完成;R5 正式候选包及三数据库、邮件、DNS、HTTPS、回滚等生产门禁仍未完成。
|
||||
- [~] 总体验收未通过,当前不具备最终上线承诺依据。
|
||||
|
||||
|
||||
## 2026年10月3日18:28 范围确认与最新任务入口
|
||||
|
||||
本节取代下方历史章节中的“当前”进度描述;后续结果继续按时间记录。用户已确认:第一台 `8.137.23.95` 用于 Scert 部署测试,第二台 `45.136.13.241` 仅用于证书推送测试;不以两台均部署 Scert 为验收要求。Smail 地址为 `web.cdis.cn`,仅处理 Linux。
|
||||
|
||||
@ -1,5 +1,29 @@
|
||||
# 部署事务人工核查任务清单
|
||||
|
||||
## 2026年10月3日19:02 边界整改结果
|
||||
|
||||
本节优先于历史记录。
|
||||
|
||||
- [x] 长路径响应分片已实现,单帧 16 KiB、数据片 8 KiB、最多四十八片;保持一次采样,完整验证后才返回结果。
|
||||
- [x] 长路径六倍 JSON 转义、后续请求、二十类异常分片、总量超限及取消用例通过;最新本机协议、管理员终端和部署三包竞态通过。
|
||||
- [x] SQLite 截止时间专项重复二十次及后端完整静态检查通过。
|
||||
- [!] 旧五包竞态四包通过,HTTP 依赖编译达到运行上限;整体退出码一。
|
||||
- [~] 最新后端全量及 HTTP 竞态继续验证,R2 仍待完整和正式包复审。
|
||||
- [ ] R3、R4 独立人工处置及持久化审计未完成,R5 未部署本轮候选包,总体验收未通过。
|
||||
|
||||
|
||||
## 2026年10月3日18:52 本机核查响应边界整改
|
||||
|
||||
本节为最新执行入口,保留后续历史记录。需求确认:只处理 Linux,第一台用于部署、第二台用于推送;核查仍为只读,不增加公开接口,不改变历史事务。
|
||||
|
||||
- [x] 修改前检查点 `75b3b71` 已提交并推送,保存数据库截止时间修复及人工处置设计。
|
||||
- [x] 修复后原生 Linux 后端全量退出码零;数据库、部署、本机协议及管理员终端四包竞态已通过,HTTP 包仍在编译,不能记为五包通过。
|
||||
- [~] R2 边界整改:合法长路径可能使完整核查结果超过 16 KiB;保持单帧上限,采用有序、有总量上限的分片,仅采样一次,不截断路径,不暴露部分结果。
|
||||
- [ ] 覆盖长路径及 JSON 转义膨胀、丢片、乱序、随机令牌变化、混合载荷、超限、取消及后续请求;完成原生 Linux 回归与静态检查。
|
||||
- [ ] R3、R4 独立人工处置及审计尚未编码;不得将只读核查当作恢复闭环。
|
||||
- [ ] R5 正式候选包部署与生产流程复审未完成,总体验收仍未通过。
|
||||
|
||||
|
||||
## 2026年10月3日18:28 最新范围与执行状态
|
||||
|
||||
本节取代下方历史“当前”状态。第一台负责 Scert 部署测试,第二台仅负责证书推送测试;R5 不再要求双机部署 Scert 主服务,已有服务不擅自卸载。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user