feat: harden loose-offline sync for user JWT, schema, and console ops

Enable Binding-scoped agent push/pull, empty-table schema ensure, SyncPage inspect/drop-table, default module import, and agent-bound publish docs from the 宇恒联调意见.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-05 09:47:35 +08:00
parent 76cdcd760e
commit b04b180d30
59 changed files with 4762 additions and 308 deletions

View File

@@ -40,9 +40,7 @@ func PushToRemote(ctx context.Context, ch *Channel, store *FileStore, item PushI
if table == "" {
return nil, fmt.Errorf("table required")
}
if !tableInChannel(ch, table) {
return nil, fmt.Errorf("table %s 不在通道白名单", table)
}
// Z4 冻结通道表白名单不再作为拒收依据Binding + JWT 才是权限源。
pkCol := "id"
if ch.PKColumns != nil && strings.TrimSpace(ch.PKColumns[table]) != "" {
pkCol = ch.PKColumns[table]
@@ -57,14 +55,21 @@ func PushToRemote(ctx context.Context, ch *Channel, store *FileStore, item PushI
version = time.Now().UnixNano()
}
db, err := Open(ch.Remote.Driver, ch.Remote.DSN)
db, err := AcquireRemote(ch.Remote.Driver, ch.Remote.DSN)
if err != nil {
return nil, fmt.Errorf("open remote: %w", err)
return nil, wrapOpenRemote(err)
}
defer db.Close()
if err := EnsureMeta(ctx, db, ch.Remote.Driver); err != nil {
return nil, fmt.Errorf("ensure meta: %w", err)
return nil, Retryablef("ensure meta: %v", err)
}
if op != "delete" {
var row map[string]any
_ = json.Unmarshal([]byte(payload), &row)
if err := EnsureTableFromRow(ctx, db, ch.Remote.Driver, table, pkCol, row); err != nil {
return nil, Retryablef("ensure table: %v", err)
}
}
res := &PushResult{
@@ -74,13 +79,16 @@ func PushToRemote(ctx context.Context, ch *Channel, store *FileStore, item PushI
tgtVer, has, err := GetMetaVersion(ctx, db, ch.Remote.Driver, table, rowPK)
if err != nil {
return nil, err
return nil, Retryablef("meta version: %v", err)
}
// 幂等:同 version 已落地 → 跳过
if has && tgtVer == version {
res.OK = true
res.Skipped = true
res.Message = "already applied (same version)"
if store != nil {
_ = store.PatchStats(ch.ID, func(c *Channel) { c.Stats.PushedSkipped++ })
}
return res, nil
}
if has && tgtVer > version {
@@ -109,11 +117,14 @@ func PushToRemote(ctx context.Context, ch *Channel, store *FileStore, item PushI
res.OK = true
res.Skipped = true
res.Message = "target newer; kept (lww_target)"
if store != nil {
_ = store.PatchStats(ch.ID, func(c *Channel) { c.Stats.PushedSkipped++ })
}
return res, nil
case PolicyLWWSource:
loser := SnapshotTargetRow(ctx, db, ch.Remote.Driver, table, pkCol, rowPK)
if err := ApplyChange(ctx, db, ch.Remote.Driver, table, pkCol, op, payload, version); err != nil {
return nil, err
return nil, Retryablef("apply: %v", err)
}
RecordLwwOverride(store, LwwOverride{
TenantID: ch.TenantID,
@@ -130,7 +141,10 @@ func PushToRemote(ctx context.Context, ch *Channel, store *FileStore, item PushI
SourceVer: version,
})
if store != nil {
_ = store.PatchStats(ch.ID, func(c *Channel) { c.Stats.PushedOK++ })
_ = store.PatchStats(ch.ID, func(c *Channel) {
c.Stats.PushedOK++
c.Stats.PushedApplied++
})
}
res.OK = true
res.Applied = true
@@ -150,7 +164,10 @@ func PushToRemote(ctx context.Context, ch *Channel, store *FileStore, item PushI
SourceVer: version,
Message: "target version newer than agent push",
})
_ = store.PatchStats(ch.ID, func(c *Channel) { c.Stats.Conflicts++ })
_ = store.PatchStats(ch.ID, func(c *Channel) {
c.Stats.Conflicts++
c.Stats.PushedSkipped++
})
}
res.OK = true
res.Skipped = true
@@ -161,10 +178,13 @@ func PushToRemote(ctx context.Context, ch *Channel, store *FileStore, item PushI
}
if err := ApplyChange(ctx, db, ch.Remote.Driver, table, pkCol, op, payload, version); err != nil {
return nil, err
return nil, Retryablef("apply: %v", err)
}
if store != nil {
_ = store.PatchStats(ch.ID, func(c *Channel) { c.Stats.PushedOK++ })
_ = store.PatchStats(ch.ID, func(c *Channel) {
c.Stats.PushedOK++
c.Stats.PushedApplied++
})
}
res.OK = true
res.Applied = true
@@ -192,12 +212,9 @@ func PushBatchToRemote(ctx context.Context, ch *Channel, store *FileStore, items
}
func tableInChannel(ch *Channel, table string) bool {
for _, t := range uniqueTables(ch.Local.Tables, ch.Remote.Tables) {
if strings.EqualFold(strings.TrimSpace(t), table) {
return true
}
}
return false
// Z4 冻结:通道 tables 仅历史兼容展示push/pull 不再据此拒收。
_ = ch
return strings.TrimSpace(table) != ""
}
func normalizePushPayload(item PushItem, pkCol string) (op string, payload string, rowPK string, err error) {