Add UUID/FK channel checks, agent whitelist/push APIs, bindings, super-admin LWW audit with rollback, reconcile rate limits, and sync docs. Default customers stay opt-in; company conflict UI is removed. Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
2.2 KiB
Go
80 lines
2.2 KiB
Go
package dbsync
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// RollbackLwwOverride 超管按落败快照回滚线上 A 单行。
|
||
// 仅 outcome=applied_source 可回滚:把 loser_payload 写回 remote;无快照则按 pk 删除。
|
||
func RollbackLwwOverride(ctx context.Context, store *FileStore, overrideID string) (*LwwOverride, error) {
|
||
if store == nil {
|
||
return nil, fmt.Errorf("store is nil")
|
||
}
|
||
o, err := store.GetLwwOverride(overrideID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if o.Outcome != OutcomeApplied {
|
||
return nil, fmt.Errorf("仅「源端覆盖」记录可回滚(当前 outcome=%s)", o.Outcome)
|
||
}
|
||
if strings.TrimSpace(o.ChannelID) == "" {
|
||
return nil, fmt.Errorf("override missing channel_id")
|
||
}
|
||
ch, err := store.GetChannel(o.ChannelID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("channel: %w", err)
|
||
}
|
||
table := strings.TrimSpace(o.Table)
|
||
pkCol := "id"
|
||
if ch.PKColumns != nil && strings.TrimSpace(ch.PKColumns[table]) != "" {
|
||
pkCol = ch.PKColumns[table]
|
||
}
|
||
db, err := Open(ch.Remote.Driver, ch.Remote.DSN)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("open remote: %w", err)
|
||
}
|
||
defer db.Close()
|
||
if err := EnsureMeta(ctx, db, ch.Remote.Driver); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
before := SnapshotTargetRow(ctx, db, ch.Remote.Driver, table, pkCol, o.RowPK)
|
||
ver := time.Now().UnixNano()
|
||
loser := strings.TrimSpace(o.LoserPayload)
|
||
op := "upsert"
|
||
payload := loser
|
||
if loser == "" || loser == "{}" {
|
||
op = "delete"
|
||
payload = fmt.Sprintf(`{%q:%q}`, pkCol, o.RowPK)
|
||
}
|
||
if err := ApplyChange(ctx, db, ch.Remote.Driver, table, pkCol, op, payload, ver); err != nil {
|
||
return nil, fmt.Errorf("apply rollback: %w", err)
|
||
}
|
||
|
||
rec := LwwOverride{
|
||
ID: uuid.NewString(),
|
||
TenantID: o.TenantID,
|
||
ChannelID: o.ChannelID,
|
||
Table: o.Table,
|
||
RowPK: o.RowPK,
|
||
Op: op,
|
||
Entry: EntryRollback,
|
||
Policy: "manual_rollback",
|
||
Outcome: OutcomeRolled,
|
||
LoserPayload: before,
|
||
WinnerPayload: payload,
|
||
TargetVer: o.SourceVer,
|
||
SourceVer: ver,
|
||
CreatedAt: time.Now().UTC(),
|
||
}
|
||
if err := store.AddLwwOverride(rec); err != nil {
|
||
return nil, err
|
||
}
|
||
return &rec, nil
|
||
}
|