Reject foreign Dispatcher before changing SQLite state
This commit is contained in:
@@ -94,6 +94,10 @@
|
||||
- 本机 HTTPS Mock 信任:Agent 上传及 Dispatcher 配置读取使用现有 mTLS CA 信任束校验证书,不放宽主机名或证书验证,仍拒绝非本机目标及跨域重定向。测试确认已批准的本机 HTTPS 证书可访问,未知证书与无效信任束均失败;未使用 `InsecureSkipVerify`。这不是对真实 OSS 的验证。
|
||||
- 已验证:`go test ./... -count=1`、`go test -race ./... -count=1`、`go vet ./...`、`go build ./...`、`PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-current-contracts.sh`、`PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-proto.sh`、`git diff --check`。P06 的本机隔离正向、失败注入和重启防重复场景已分别通过;根命令在 MQ 长时故障后的完整恢复仍需 P08 验证,真实外部服务、SaaS 应用收讫与线路均未验收,不冒充生产通过。
|
||||
|
||||
## P07 单一入口清理(执行中)
|
||||
|
||||
- 数据归属根因:现行 SQLite 的 `CloseAdmission` 原本只按传入的 Dispatcher ID 插入或更新状态;把已有未投递 outbox 的 D1 数据库交给 D2 启动时,会错误建立第二个归属状态。新测试先证实该问题,再要求同一事务内检查既有归属,冲突时返回身份错误且保留原归属和未确认 outbox;本轮不迁移、不删除任何既有数据库。旧入口及名称残留仍须分批清理,不能因该测试通过便宣称 P07 完成。
|
||||
|
||||
## 验收台账
|
||||
|
||||
A01–A12 的行为验证及 K01–K16 的运行时验证待 P03–P08 逐项填充;不得用本地 Mock 冒充外部签收。
|
||||
P01–P06 的项目内隔离证据见上;P07 全仓命名与唯一入口尚在清理,A01–A12 和 K01–K16 的最终对照仍待 P08。不得用本地 Mock 冒充外部签收。
|
||||
|
||||
@@ -193,10 +193,26 @@ func (s *CurrentStore) CloseAdmission(dispatcherID string) error {
|
||||
if dispatcherID == "" {
|
||||
return errors.New("dispatcher ID is required")
|
||||
}
|
||||
if _, err := s.db.Exec(`INSERT INTO dispatcher_state(dispatcher_id,discovery_ready) VALUES(?,0)
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("begin Dispatcher admission fence: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
var boundID string
|
||||
checkErr := tx.QueryRow(`SELECT dispatcher_id FROM dispatcher_state WHERE dispatcher_id<>? LIMIT 1`, dispatcherID).Scan(&boundID)
|
||||
if checkErr == nil {
|
||||
return fmt.Errorf("%w: stored=%s requested=%s", ErrDispatcherIdentityMismatch, boundID, dispatcherID)
|
||||
}
|
||||
if !errors.Is(checkErr, sql.ErrNoRows) {
|
||||
return fmt.Errorf("read SQLite Dispatcher owner: %w", checkErr)
|
||||
}
|
||||
if _, err := tx.Exec(`INSERT INTO dispatcher_state(dispatcher_id,discovery_ready) VALUES(?,0)
|
||||
ON CONFLICT(dispatcher_id) DO UPDATE SET discovery_ready=0`, dispatcherID); err != nil {
|
||||
return fmt.Errorf("close task admission: %w", err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("commit Dispatcher admission fence: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCurrentStoreRejectsForeignDispatcherWithoutTouchingOutbox(t *testing.T) {
|
||||
const foreignID = "862c8e9b-26b8-4642-9fac-c7d85f487a30"
|
||||
const eventID = "retained-outbox-event"
|
||||
const originalBody = `{"proof":"original"}`
|
||||
path := filepath.Join(t.TempDir(), "dispatcher.sqlite")
|
||||
owner, err := OpenCurrent(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := owner.CloseAdmission(currentDispatcherID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := owner.db.Exec(`INSERT INTO dispatcher_outbox(dispatcher_id,event_id,event_type,routing_key,body,confirmed)
|
||||
VALUES(?,?,?,?,?,0)`, currentDispatcherID, eventID, "call.execute.result", "agent-call.saas.events.v1", []byte(originalBody)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := owner.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
foreign, err := OpenCurrent(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer foreign.Close()
|
||||
if err := foreign.CloseAdmission(foreignID); !errors.Is(err, ErrDispatcherIdentityMismatch) {
|
||||
t.Fatalf("foreign Dispatcher admitted to an existing database: %v", err)
|
||||
}
|
||||
var count int
|
||||
var survivingID string
|
||||
if err := foreign.db.QueryRow(`SELECT COUNT(*), MIN(dispatcher_id) FROM dispatcher_state`).Scan(&count, &survivingID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 1 || survivingID != currentDispatcherID {
|
||||
t.Fatalf("foreign startup changed the original Dispatcher identity: count=%d identity=%q", count, survivingID)
|
||||
}
|
||||
var body []byte
|
||||
var confirmed int
|
||||
if err := foreign.db.QueryRow(`SELECT body,confirmed FROM dispatcher_outbox WHERE dispatcher_id=? AND event_id=?`, currentDispatcherID, eventID).Scan(&body, &confirmed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(body) != originalBody || confirmed != 0 {
|
||||
t.Fatal("foreign startup changed an unconfirmed original outbox record")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user