fix(ssf): 支持断开后安全重连

重建 Stream 时仅重置流级 Verification 状态,保留历史收据和撤销水位;允许显式恢复失败连接,并在进程重启后按持久化时间继续 bootstrap。\n\n已通过 pnpm test、pnpm lint、pnpm build、go vet、PostgreSQL 集成测试以及本地真实断开重连和停机重试验收。
This commit is contained in:
2026-07-16 12:30:14 +08:00
parent 192e924dfb
commit 8e33d1d33e
4 changed files with 127 additions and 15 deletions
@@ -155,4 +155,31 @@ func TestSecurityEventWatermarkVerificationAndFallbackLifecycle(t *testing.T) {
if lifecycle != "enabled" || lastError != nil {
t.Fatalf("recovered connection lifecycle=%q lastError=%v", lifecycle, lastError)
}
replacementStreamID := uuid.NewString()
if err := db.EnsureSecurityEventStreamState(ctx, issuer, audience, replacementStreamID); err != nil {
t.Fatalf("rebind retained receiver state: %v", err)
}
var reboundStreamID, reboundMode, reboundStatus string
var reboundVerification *time.Time
if err := db.pool.QueryRow(ctx, `SELECT stream_id::text,mode,stream_status,last_verification_at
FROM gateway_security_event_stream_state WHERE issuer=$1 AND audience=$2`, issuer, audience).
Scan(&reboundStreamID, &reboundMode, &reboundStatus, &reboundVerification); err != nil {
t.Fatal(err)
}
if reboundStreamID != replacementStreamID || reboundMode != "bootstrap" || reboundStatus != "unknown" || reboundVerification != nil {
t.Fatalf("rebound state stream=%q mode=%q status=%q verification=%v",
reboundStreamID, reboundMode, reboundStatus, reboundVerification)
}
var retainedReceipts, retainedWatermarks int
if err := db.pool.QueryRow(ctx, `SELECT count(*) FROM gateway_security_event_receipts
WHERE issuer=$1`, issuer).Scan(&retainedReceipts); err != nil {
t.Fatal(err)
}
if err := db.pool.QueryRow(ctx, `SELECT count(*) FROM gateway_oidc_revocation_watermarks
WHERE issuer=$1 AND tenant_id=$2 AND subject=$3`, subjectIssuer, tenantID, subject).Scan(&retainedWatermarks); err != nil {
t.Fatal(err)
}
if retainedReceipts == 0 || retainedWatermarks != 1 {
t.Fatalf("rebind discarded security history receipts=%d watermarks=%d", retainedReceipts, retainedWatermarks)
}
}