syncwire

package module
v0.0.0-...-7875149 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: GPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package syncwire 是桌面端 ↔ server 工作区同步协议的线上契约:结构、词表、上限, 以及载荷守卫(guard.go)—— 两个宿主跑的是同一份守卫,而不是两份对齐的抄本。

桌面端 internal/pkg/syncwire 与本包同名,分工是清楚的:协议归本包,那边只做别名 再导出加上本端专属的东西(业务码的客户端表达、账号级实时通道的解码)。

字段的标签在这条协议里是**承重的**:PushItem.Payload 少一个 omitempty,墓碑就会带上 JSON null,server 的 GuardPayload 判 ErrPayloadNotObject、把这一条单独拒掉,这次删除 就传不到别的设备。syncwire_test.go 把它钉住了。

一个结构同时承担两侧的角色,所以两套标签都要在:json 标签管桌面端的编码与服务端的 解码,binding 标签管服务端的入参校验。binding 标签是惰性的,只有 gin 绑定时才读, 本 module 因此零外部依赖。

Index

Constants

View Source
const (
	KindProject         = "project"
	KindDepartment      = "department"
	KindAgent           = "agent"
	KindAgentBackend    = "agent_backend"
	KindAgentExecTarget = "agent_exec_target"
	KindProjectAgent    = "project_agent"
	KindProjectLocation = "project_location"
	KindLLMProvider     = "llm_provider"
	KindAgentBackendCLI = "agent_backend_cli"
	// KindLabel / KindIssue / KindIssueLabel 是看板并入账号级同步组带来的三个类型。
	// 它们互相引用的方向是 label ← issue_label → issue,任务本身还引用项目、Agent
	// 与 backend —— 全部用同步标识表达,载荷里没有一个本地自增 ID。
	KindLabel      = "label"
	KindIssue      = "issue"
	KindIssueLabel = "issue_label"
)

同步组承载的对象类型,与 server 的 sync_entity 逐字一致。

View Source
const (
	// PushStatusAccepted 基版本与该行当前版本相符,或该同步标识 server 从未见过。
	PushStatusAccepted = "accepted"
	// PushStatusConflict 基版本与当前版本不符,或基版本为空但同步标识已存在。
	// 本次上行按后到者胜照常生效,应答里回报被覆盖的版本与来源设备。
	PushStatusConflict = "conflict"
	// PushStatusRejected 这一条没有生效,原因见 Reason。
	PushStatusRejected = "rejected"
)
View Source
const (
	// PushRejectReasonDeleted 该对象在 server 上已是墓碑。删除不会被复活,恢复动作
	// 因此明确失败;界面据此提供「按这份内容新建」—— 那是一个新的同步标识。
	PushRejectReasonDeleted = "deleted"
	// PushRejectReasonKind 对象类型不属于同步组、与该同步标识已有行的类型不符,
	// 或缺少该类型必需的自然键。
	PushRejectReasonKind = "kind_invalid"
	// PushRejectReasonPayload 载荷过不了服务端 ValidatePayload 的守卫。
	PushRejectReasonPayload = "payload_rejected"
)

单条拒绝的原因。

**凡是能拒掉一条的理由,都只拒那一条。** 整批拒是一个永久性的堵:上行端整批失败时 一行都不出队,下一轮再发同一批、再被同一条拒掉,那台机器的上行队列从此不动 —— 连 删除也传不出去。校验不通过的行以 rejected 回报,上行端据此把它移出队列并记进 「没能同步的改动」。

View Source
const (
	MaxPushBatch      = 500
	MaxPullLimit      = 1000
	MaxLocalPathItems = 2000
)

MaxPushBatch / MaxPullLimit / MaxLocalPathItems 是三个请求的批量上限。

它们与 agentre-server 那三条 gin 标签里的字面量逐字相符 —— 标签写不了常量引用, 所以那一致性由消费仓自己的守卫盯着。桌面端一次实际发多少是它自己的选择,只要 不超过 MaxPushBatch。

View Source
const CodeCursorUnknown = 30505

CodeCursorUnknown 是「下行游标超出本账号版本序列的头」的业务码:那段历史 server 不认识 —— 库被重建,或用户换了一套自建服务端。

View Source
const CodeResyncRequired = 30500

CodeResyncRequired 是「设备距上次成功同步已超过墓碑保留窗口」的业务码。

Variables

View Source
var ErrPayloadAvatarContent = errors.New("sync payload carries avatar content instead of a content hash")

ErrPayloadAvatarContent 表示载荷里出现了头像正文而不是内容哈希(R16a)。

View Source
var ErrPayloadCredential = errors.New("sync payload carries a credential or a provider row")

ErrPayloadCredential 表示载荷里出现了凭据或 provider 行正文。

View Source
var ErrPayloadLocalID = errors.New("sync payload carries a local auto-increment id")

ErrPayloadLocalID 表示载荷里出现了本地自增 ID。

View Source
var ErrPayloadNotObject = errors.New("sync payload must be a json object")

ErrPayloadNotObject 表示载荷不是一个 JSON 对象。

Kinds 是同步组的全部对象类型,按「被引用者在前」排列。

常量表与**成员资格**都只在这里:两个宿主要是各自枚举成员资格,任何一边漏掉一个新 kind,那一类对象就在那一端整类静默不同步 —— 没有报错,只是没有。两个宿主都只引用它。

次序是承重的:认领(R12a)与任何需要遍历全部类型的地方都按它走,父行因此先入队、 先落地,R2a 的暂缓少绕一圈。追加新 kind 时按引用方向插进去,别随手贴到末尾。

Functions

func GuardPayload

func GuardPayload(kind string, payload []byte) error

GuardPayload 是同步载荷的结构守卫。kind 让唯一可携带 API Key 的 llm_provider 与所有其它对象明确分开;调用者不可省略它。它按**键名**挡住三类东西:

  • 本地自增 ID(键名以 id 结尾、取值是数字)。跨机引用一律用同步标识、agentred 指纹或 provider_key 表达,它们全是字符串;这个形状只可能是某台机器的本地主键, 在别的机器上指向完全不同的对象。
  • `api_key` 这个键,以及整行 provider 正文(`provider` / `providers` 取值是 对象或数组)。只有 kind=llm_provider 能携带 api_key;其它对象只传 provider_key 这个字符串。agent_backend 身份载荷也不得含 cli_path。
  • `avatar_data_url`:头像正文按内容哈希单独传,不进同步载荷。

**它挡不住、也没打算挡的:载荷里以 JSON 字符串形式携带的正文。** 最要紧的一处是 backend 的 `env_json`——它是用户自填的透传环境变量表,是 backend 配置的一部分, 按设计随 backend 上行,在账号下明文存放。里面不会有 App 自管的那 15 个保留键 (ANTHROPIC_API_KEY / OPENAI_API_KEY 等,由桌面端的 agent_backend_entity.Check 拒绝入库),但用户自己往里放的任何别的密钥都会原样过机。

这条守卫不是凭据扫描器,别把它当成一个——要挡住 env_json 就得整个字段不上行, 那是规格决定,不是守卫能顺手做掉的事。

**两个宿主跑的是这一份实现,不是两份对齐的抄本。** 桌面端在上行前调用它,让坏载荷 根本发不出去;服务端在落库前调用它,保护账号里的其它设备。守卫零外部依赖,这个 module 正是为「服务端也能 import」而存在的。

规则一旦分叉,后果是单条被拒(server 回 rejected,本端出队并进 R5 列表),不会堵住 整条队列 —— 所以分叉不会自己暴露,只能靠同一份实现防住。

空载荷合法:墓碑不带正文。

func KindValid

func KindValid(kind string) bool

KindValid 报告某个对象类型是否属于同步组。取值域是闭合的:不认识的类型一律按 单条拒绝处置(PushRejectReasonKind),不会连累整批。

func PayloadFor

func PayloadFor(kind string) (any, bool)

PayloadFor 按对象类型造一份新的空载荷,返回的是**指针**,可以直接交给 json.Unmarshal。不认识的类型返回 (nil, false) —— 取值域与 Kinds 一样是闭合的。

知道自己在解什么的调用方直接用具体类型(`var p syncwire.IssuePayload`)更清楚; 这个口子是给按 kind 分发、拿不到静态类型的那几处用的。

**它不是写路径的工具。** sync_objects 是整行 last-write-wins,把一份载荷解进 结构体再 marshal 回去,会把这个类型当下不认识的键(对端新版本刚加的)静默抹掉。 写一个键就走 map[string]any 往返,只覆盖这次真的涉及的键。

Types

type AgentBackendCLIPayload

type AgentBackendCLIPayload struct {
	CLIPath string `json:"cli_path"`
}

AgentBackendCLIPayload 是 kind=agent_backend_cli 的载荷:某个后端在某台机器上的 可执行文件路径。

它与 project_location 同形 —— 只带正文,身份(后端同步标识, agentred 指纹)在 ScopeSyncID / AgentredFingerprint 两列上,绝不进后端自己的身份载荷。这一条覆盖 缺席只意味着那台机器上走 PATH,账号级身份在每一端照常可用。

type AgentBackendConfig

type AgentBackendConfig struct {
	// ModelRoutes 是嵌套的 JSON 对象而不是字符串:套一层字符串会把
	// `{"OPUS":{…}}` 转义成 `"{\"OPUS\":…}"`,人读不了,工具也进不去。
	ModelRoutes           json.RawMessage `json:"modelRoutes,omitempty"`
	Sandbox               string          `json:"sandbox,omitempty"`
	Approval              string          `json:"approval,omitempty"`
	DefaultPermissionMode string          `json:"defaultPermissionMode,omitempty"`
	DefaultModel          string          `json:"defaultModel,omitempty"`
	OpenClawGatewayURL    string          `json:"openclawGatewayUrl,omitempty"`
	OpenClawAgentID       string          `json:"openclawAgentId,omitempty"`
	OpenClawDefaultModel  string          `json:"openclawDefaultModel,omitempty"`
	OpenClawSessionMode   string          `json:"openclawSessionMode,omitempty"`
	HermesURL             string          `json:"hermesUrl,omitempty"`
	// HermesAuthProvider / HermesUserID 是 gated serve 的非敏感展示字段。refresh
	// token 绝不进这里(它只在桌面端 keychain 里)。
	HermesAuthProvider string `json:"hermesAuthProvider,omitempty"`
	HermesUserID       string `json:"hermesUserId,omitempty"`
	// ACPCommand / ACPArgs 仅 acp 使用:外部 ACP Agent 的可执行文件与附加 argv。
	// 它们是账号级身份(与 hermesUrl 同形),没有每设备覆盖通路 —— acp 没有
	// 「已知 CLI」的概念,可执行文件由 backend 自己声明。
	ACPCommand string   `json:"acpCommand,omitempty"`
	ACPArgs    []string `json:"acpArgs,omitempty"`
}

AgentBackendConfig 是后端**单类型独占设置**的唯一键表:桌面端 agent_backends.config_json 列、同步载荷的 config 对象与 web API 用的是同一个形状、同一组 camelCase 键。

每个键都 omitempty:「没配」在线上就是键不在,全空编成 {}。这里的零值都是合法取值 (空 sandbox = 走 CLI 默认,空网关地址 = 还没配),哪个键属于哪种后端类型由桌面端的 后端实体校验,契约只定形状。

type AgentBackendPayload

type AgentBackendPayload struct {
	Type            string `json:"type"`
	Name            string `json:"name"`
	ProviderKey     string `json:"provider_key"`
	ModelKey        string `json:"model_key"`
	EnvJSON         string `json:"env_json"`
	ReasoningEffort string `json:"reasoning_effort"`
	// Config 是后端的单类型独占设置,整份过机:接收端用它**整体替换**本地那一份,
	// 缺席的键即为空(缺整个 config 等同 {})。
	Config AgentBackendConfig `json:"config"`
}

AgentBackendPayload 是 kind=agent_backend 的载荷:后端的**账号级身份**。

供应商配置自成一个对象(kind=llm_provider),这里只带 ProviderKey 这个稳定字符串 键。cli_path 是每设备覆盖,自成一个对象(kind=agent_backend_cli),**永不出现在 这里** —— GuardPayload 对 kind=agent_backend 明确挡住 cli_path 这个键。

运行设备同样不是载荷里的键:它走上行/下行项自己的 agentred_fingerprint 列, 后端在一台桌面端上配好之后,在每一端与 server 上指的都是同一台机器。

**EnvJSON 是用户自填的透传环境变量表,按设计随后端上行,在账号下明文存放。** App 自管的那批保留键(ANTHROPIC_API_KEY 等)由桌面端的实体校验拒绝入库,但用户 自己往里放的任何别的密钥都会原样过机 —— GuardPayload 不是凭据扫描器,挡不住以 JSON 字符串形式携带的正文(见它的文档)。

type AgentExecTargetPayload

type AgentExecTargetPayload struct {
	AgentSyncID   string `json:"agent_sync_id"`
	BackendSyncID string `json:"backend_sync_id"`
	// SortOrder 是这一档在链上的位次;0 基,越小越先用。
	SortOrder int `json:"sort_order"`
	// SkillsJSON 是这一档的技能授权(R15e:授权下沉到档,不在 Agent 行上)。
	SkillsJSON string `json:"skills_json"`
}

AgentExecTargetPayload 是 kind=agent_exec_target 的载荷:Agent 的一档执行目标。

type AgentPayload

type AgentPayload struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	AvatarColor string `json:"avatar_color"`
	AvatarIcon  string `json:"avatar_icon"`
	// AvatarHash 是自定义头像正文的内容哈希;空串 = 没有自定义头像。正文按这个哈希
	// 单独取(R16a),取不到时接收端保留本机已有的那份,不阻塞这一行落地。
	AvatarHash string `json:"avatar_hash,omitempty"`
	// SystemBadge 非空即那一个系统 Agent(唯一合法的「既不属于部门也没有上级」)。
	SystemBadge string `json:"system_badge"`
	// DepartmentSyncID 与 ParentAgentSyncID 是归属的二选一,两个都要如实带出:
	// 接收端的归属选择器靠这两个键决定自己停在哪一组上,不能只带推导后的结果。
	DepartmentSyncID  string `json:"department_sync_id,omitempty"`
	ParentAgentSyncID string `json:"parent_agent_sync_id,omitempty"`
	SortOrder         int    `json:"sort_order"`
	// PromptJSON / ToolsJSON 是两份 JSON 字符串,形状归桌面端所有,契约只搬运不解析
	// —— 解析它就等于在两个宿主里各维护一份同一个结构。
	PromptJSON string `json:"prompt_json"`
	ToolsJSON  string `json:"tools_json"`
	Pinned     bool   `json:"pinned"`
}

AgentPayload 是 kind=agent 的载荷。

里面只有 AvatarHash 而没有头像正文:正文按内容哈希单独走一条路(R16a), 一律不进同步载荷(GuardPayload 挡住 avatar_data_url 这个键)。也没有 skills_json —— 技能授权下沉到执行目标行(R15e)。

type DepartmentPayload

type DepartmentPayload struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Icon        string `json:"icon"`
	AccentColor string `json:"accent_color"`
	// ParentSyncID 为空即根部门。
	ParentSyncID string `json:"parent_sync_id,omitempty"`
	// LeadAgentSyncID 是部门负责人的同步标识。
	//
	// 它与 department ↔ agent 之间存在**引用环**:负责人必须是本部门成员,所以那个
	// Agent 的落地又等着这个部门。接收端因此不能把它当成阻塞引用 —— 两边都阻塞就是
	// 死锁,两行一起躺到过期(桌面端 sync_svc/adapter_org.go 的 refs 注释与
	// adapter_refcycle_test.go 是那道守卫)。
	LeadAgentSyncID string `json:"lead_agent_sync_id,omitempty"`
	SortOrder       int    `json:"sort_order"`
}

DepartmentPayload 是 kind=department 的载荷。

type IssueLabelPayload

type IssueLabelPayload struct {
	IssueSyncID string `json:"issue_sync_id"`
	LabelSyncID string `json:"label_sync_id"`
}

IssueLabelPayload 是 kind=issue_label 的载荷:任务 ↔ 标签的关联。 关联表的主键是 (issue_id, label_id) 两个本地自增值,在另一台机器上指向完全不同的 两行,因此两端都只能用同步标识表达。

type IssuePayload

type IssuePayload struct {
	Title       string `json:"title"`
	Description string `json:"description"`
	Stage       string `json:"stage"`
	// Position 是同一列内的排序位置,取浮点是为了在两张卡之间插入而不重排整列。
	Position           float64 `json:"position"`
	ProjectSyncID      string  `json:"project_sync_id,omitempty"`
	AgentSyncID        string  `json:"agent_sync_id,omitempty"`
	AgentBackendSyncID string  `json:"agent_backend_sync_id,omitempty"`
	LLMProviderKey     string  `json:"llm_provider_key"`
	LLMModelKey        string  `json:"llm_model_key"`
	// ClosedAt 是完成时刻(Unix 毫秒),0 = 未完成。
	ClosedAt int64 `json:"closed_at"`
}

IssuePayload 是 kind=issue 的载荷。

载荷里**没有运行态**:agent_status、session_id 与 source 是某一台机器上这一轮跑 成什么样,跨机没有意义,也不该让另一端的界面显示一个它并不持有的会话。也没有 state —— 它完全由 Stage 推导(stage=done 即已完成),两端各自算。

执行归属的四个字段里,Agent 与后端是账号级对象、用同步标识表达;供应商与模型 本来就是稳定的字符串键。

type LLMProviderModel

type LLMProviderModel struct {
	// ModelKey 是账号内稳定的模型键,别的对象(后端、任务)按它引用模型。
	ModelKey string `json:"model_key"`
	// ModelID 是发给供应商 API 的那个字符串。它以 id 结尾但**是字符串**,因此不落进
	// GuardPayload 的本地自增 ID 那一条。
	ModelID string `json:"model_id"`
	Name    string `json:"name"`
	Enabled bool   `json:"enabled"`
	// ContextWindow / MaxOutput 为 0 = 未知,带 omitempty 一并缺席:线上「没填」与
	// 「填了 0」不该长成同一个样子。
	ContextWindow int `json:"context_window,omitempty"`
	MaxOutput     int `json:"max_output,omitempty"`
}

LLMProviderModel 是内嵌在 LLMProviderPayload 里的一行模型。它**不是**一个 kind: 模型跟着供应商整行走。

type LLMProviderPayload

type LLMProviderPayload struct {
	Name    string `json:"name"`
	Type    string `json:"type"`
	BaseURL string `json:"base_url"`
	APIKey  string `json:"api_key"`
	// DefaultModelKey 指向 Models 里某一项的 ModelKey。
	DefaultModelKey string             `json:"default_model_key"`
	Enabled         bool               `json:"enabled"`
	Models          []LLMProviderModel `json:"models"`
}

LLMProviderPayload 是 kind=llm_provider 的载荷,**唯一携带 API Key 的对象** (GuardPayload 只对这一个 kind 放行 api_key 这个键)。

它的同步标识就是 ProviderKey 本身:别的对象跨机引用供应商时只传这个稳定字符串键 (决策 6),因此这个对象没有第二套标识。

模型行**内嵌**而不另起一个 kind:model_key 由此保持稳定,又不必为一张从属表再造 一整套同步对象。

type LabelPayload

type LabelPayload struct {
	Name   string `json:"name"`
	Tone   string `json:"tone"`
	Status int    `json:"status"`
}

LabelPayload 是 kind=label 的载荷。

Status 在载荷里,而别的对象的存活 / 墓碑只靠 DeletedAt 表达:server 没有本地行, 它的看板读路径把 sync_objects 的载荷直接拼成响应,没有这一列就判不出一个标签还 在不在。桌面端落地时**不**读它 —— 一条活着的下行项按定义就是活的(决策 20)。

type LocalPathItem

type LocalPathItem struct {
	ProjectSyncID string `json:"project_sync_id" binding:"required,max=128"`
	Path          string `json:"path"            binding:"max=1024"`
}

LocalPathItem 是上报组的一条:某个项目在这台设备上的真实本机路径。

与同步组的那些表无关 —— 本机路径不在桌面端之间流动,只单向上报给 server, 按设备分命名空间存放。

type ProjectAgentPayload

type ProjectAgentPayload struct {
	ProjectSyncID string `json:"project_sync_id"`
	AgentSyncID   string `json:"agent_sync_id"`
	// JoinedAt 是加入时刻(Unix 毫秒)。成员关系没有别的可改内容:它要么在、要么不在。
	JoinedAt int64 `json:"joined_at"`
}

ProjectAgentPayload 是 kind=project_agent 的载荷:项目 ↔ Agent 的成员关系。 关系表的主键是两个本地自增值,因此两端都只能用同步标识表达。

type ProjectLocationPayload

type ProjectLocationPayload struct {
	Path string `json:"path"`
}

ProjectLocationPayload 是 kind=project_location 的载荷:某个项目在某台 agentred 上的路径。

只带路径正文。账号内自然键(项目同步标识, agentred 指纹)**在列上**—— ScopeSyncID 装项目、AgentredFingerprint 装机器,server 据此按 R4b 合并。

type ProjectPayload

type ProjectPayload struct {
	Name        string `json:"name"`
	Icon        string `json:"icon"`
	Color       string `json:"color"`
	Description string `json:"description"`
	// ParentSyncID 为空即根项目。项目树的父引用,跨机用同步标识表达。
	ParentSyncID string `json:"parent_sync_id,omitempty"`
	// SortOrder 是同一层内的手工排序位次。
	SortOrder int `json:"sort_order"`
}

ProjectPayload 是 kind=project 的载荷。

**没有 path**:本机路径住在桌面端的 projects.path,只上报、不同步(决策 6、R9); 同步进来的项目在接收端是「未配置路径」状态(R10)。也没有 status —— 存活 / 墓碑 由上行/下行项的 DeletedAt 表达,本地 status 是它的本机投影。

type PullItem

type PullItem struct {
	Kind                string          `json:"kind"`
	SyncID              string          `json:"sync_id"`
	ScopeSyncID         string          `json:"scope_sync_id,omitempty"`
	AgentredFingerprint string          `json:"agentred_fingerprint,omitempty"`
	Payload             json.RawMessage `json:"payload"`
	Version             int64           `json:"version"`
	UpdatedAt           int64           `json:"updated_at"`
	// OriginFingerprint 是最后一次修改来自哪台机器(决策 14:跨机引用一律用指纹,
	// 数值设备主键是 server 的本地键,桌面端离线创建的行没有它)。空串 = 服务端直写。
	OriginFingerprint string `json:"origin_fingerprint"`
	// DeletedAt 非零 = 墓碑,值是删除时刻(Unix 毫秒,决策 20)。
	DeletedAt int64 `json:"deleted_at"`
}

PullItem 是下行的一行,墓碑也在其中(DeletedAt > 0),删除靠它到达各端。

type PullPage

type PullPage struct {
	Items      []PullItem `json:"items"`
	NextCursor int64      `json:"next_cursor"`
	HasMore    bool       `json:"has_more"`
}

PullPage 是一次下行的一页。

type PushItem

type PushItem struct {
	Kind   string `json:"kind"    binding:"required,max=32"`
	SyncID string `json:"sync_id" binding:"required,max=128"`
	// BaseVersion 是发起端最后一次见到的同步版本号;本端新建、server 从未见过的行填 0。
	BaseVersion int64 `json:"base_version"`
	// UpdatedAt 是发起端的最后修改时间,只用于展示与 30 天窗口计算,不参与冲突裁决。
	UpdatedAt int64 `json:"updated_at"`
	// DeletedAt 非零表示这是一条墓碑,值是发起端记下的删除时刻(Unix 毫秒)。
	// 契约上是**时刻**而不是布尔:发起端库、线格式与 server 库三处都是时刻,压成布尔
	// 之后 server 落地只能另行编造一个删除时间(2026-08-27-schema-overhaul 决策 20)。
	DeletedAt           int64  `json:"deleted_at"`
	AgentredFingerprint string `json:"agentred_fingerprint" binding:"max=128"`
	// ScopeSyncID 装什么取决于 kind(project_location 装项目、agent_backend_cli
	// 装后端),与 server 的 sync_objects.scope_sync_id 同义。
	ScopeSyncID string `json:"scope_sync_id" binding:"max=128"`
	// Payload 的 omitempty 是**承重的**:墓碑不带正文,而 json.RawMessage 的零值编出
	// 来是 JSON null —— null 不是对象,server 的 GuardPayload 会把这一条拒掉,这次删除
	// 就传不到别的设备。类型必须是 json.RawMessage 而不是 []byte,否则
	// encoding/json 会把整份文档编成 base64。
	Payload json.RawMessage `json:"payload,omitempty"`
}

PushItem 是一次上行里的一条改动。

type PushResult

type PushResult struct {
	SyncID string `json:"sync_id"`
	Kind   string `json:"kind"`
	// Version 是 server 为这次上行分配的新版本号;被拒时是 server 上的当前版本。
	Version                      int64           `json:"version"`
	Status                       string          `json:"status"`
	Reason                       string          `json:"reason,omitempty"`
	OverwrittenVersion           int64           `json:"overwritten_version,omitempty"`
	OverwrittenOriginFingerprint string          `json:"overwritten_origin_fingerprint,omitempty"`
	OverwrittenPayload           json.RawMessage `json:"overwritten_payload,omitempty"`
	MergedSyncID                 string          `json:"merged_sync_id,omitempty"`
	MergedVersion                int64           `json:"merged_version,omitempty"`
	// MergedOriginFingerprint 是落败那一份来自哪台机器(决策 14);空串 = 服务端直写。
	MergedOriginFingerprint string `json:"merged_origin_fingerprint,omitempty"`
}

PushResult 是一条上行的处置结果。

Overwritten* 只在 Status 为 conflict 时有值:被这次上行覆盖掉的是哪一版、来自哪台 机器、正文是什么。正文只有 server 有 —— 上行端手上那一份是**覆盖别人的**那一份, 「追回被覆盖的那一版」靠它。

Merged* 只在自然键合并发生时有值:落败那一份的同步标识、版本与来源机器,它已在 server 落墓碑。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL