SyncEngine 技术设计
# SyncEngine 技术设计
修订记录
| 版本 | 日期 | 修订说明 |
|---|---|---|
| v0.1 | 2026-05-01 | 建立当前设计基线;延续 kunora-wiki 作为 llm-wiki 方案的既有产品封板和工程设计,不推倒重来。 |
1. 摘要
SyncEngine 负责把多个来源项目中的文档变更转换为 kunora-wiki publish/** 工作区中的受控同步变更。它读取 ConfigManager 生成的 SourceConfig,通过 SourceReader 获取来源文件,通过 WorkspaceStore 读取当前正式文档和 state,执行三方比较,输出 SourceChanges、更新 SourceLock,并把需要人工审查的变更交给 ReviewBridge。
SyncEngine 不直接合并到 main,不触发展示发布、索引或 agent 改写。任何冲突、删除或高风险变更都必须以报告形式进入 review 流程。
2. 模块目标
- 根据
SourceConfig扫描 enabled source。 - 对来源文件、上次锁定状态和当前
publish/**内容执行三方比较。 - 生成
SyncChange和SourceChanges。 - 根据同步结果安全更新
state/source-lock.json。 - 对冲突、删除、来源不可用和部分失败输出标准错误和报告。
- 为 ReviewBridge 提供可创建 Sync PR 的稳定输入。
3. 非目标
- 不直接写 main 分支或合并 PR。
- 不决定内容语义质量是否合格。
- 不调用 Docusaurus、RAGFlow、Meilisearch 或 Answer API。
- 不触发 AgentBridge 自动改写文档。
- 不绕过 WorkspaceStore 直接写
publish/**或state/**。 - 不把 source repo 的私有元数据暴露给下游公共契约。
4. 上下文边界
SyncEngine 位于 L1 Governance 层。它可以依赖 ConfigManager、WorkspaceStore 和 SourceReader/GitProvider adapter,但不能依赖 Display Adapter、Index Adapter、Answer API 或 AgentBridge。
5. 输入与输出
5.1 输入
| 输入 | 来源 | 说明 |
|---|---|---|
ConfigBundle.sources | ConfigManager | source 列表、路径映射、同步策略。 |
| source file snapshot | SourceReader/GitProvider | 来源 repo/ref 下的文件内容和 commit。 |
current DocumentRecord[] | WorkspaceStore | 当前 publish/** 文档状态。 |
SourceLock | WorkspaceStore/state | 上次成 功同步的 source commit 和文件 hash。 |
| run options | workflow/manual | dry-run、strict mode、指定 source、重试模式。 |
5.2 输出
| 输出 | 消费者 | 说明 |
|---|---|---|
SourceChanges | ReviewBridge、维护者 | 本次同步变更、冲突、错误和摘要。 |
SourceLock update | SyncEngine、ReviewBridge | 成功同步后的 source 锁定状态。 |
| candidate file writes | WorkspaceStore / ReviewBridge | Sync PR 分支中的 publish/** 修改。 |
ErrorObject[] | ReviewBridge、CI | source 不可用、冲突、lock 更新失败等错误。 |
6. 依赖的 common 契约
| 契约 | 用途 |
|---|---|
SourceConfig | 决定 source repo/ref/path 到 publish path 映射。 |
ConfigBundle | 读取经过校验的同步配置。 |
DocumentRecord | 获取当前 publish 内容状态。 |
ContentHash / NormalizedPath | 比较来源和目标文件。 |
SourceLock | 记录上次同步基线。 |
SyncChange | 单文件同步动作。 |
SourceChanges | 本次同步报告。 |
ErrorObject | 同步错误和警告。 |
RunId | 关联一次同步运行。 |
7. 三方比较模型
SyncEngine 对每个 source file 使用 A/B/C 三方模型:
| 版本 | 含义 | 来源 |
|---|---|---|
| A | 上次成功同步时的 source hash / publish hash | SourceLock |
| B | 当前 source 文件 hash | SourceReader |
| C | 当前 publish/** 文件 hash | WorkspaceStore |
动作判断:
| A | B | C | 动作 | 说明 |
|---|---|---|---|---|
| 无 | 有 | 无 | add | 新来源文件,目标不存在。 |
| 无 | 有 | 有 | conflict | 新来源映射到已有目标。 |
| 有 | 有且 B=A | C=A | keep | 来源和目标均未变。 |
| 有 | 有且 B!=A | C=A | update | 来源更新,目标未被本地改动。 |
| 有 | 有且 B=A | C!=A | keep_local | 来源未变,目标被人工改动。 |
| 有 | 有且 B!=A | C!=A | conflict | 来源和目标同时变化。 |
| 有 | 无 | C=A | delete 或 report_delete | 来源删除,目标未改,按 deletePolicy 处理。 |
| 有 | 无 | C!=A | delete_conflict | 来源删除但目标被改动。 |
MVP 默认 deletePolicy=report-only,即上游删除不会自动删除正式内容,只生成报告。
keep_local 和 report_delete 是合法 SyncChange.action,用于让 ReviewBridge 区分“本地人工保留”和“来源删除但仅报告”的治理语义。
8. 核心流程
流程要求:
- 每个 source 独立处理;单个 source 失败不应阻断所有 source,除非配置错误是全局阻断。
- 对 failed source 不更新其
SourceLock。 - 对存在 conflict 的文件不写入候选内容。
SourceChanges必须包含 skipped/failed/conflict 统计。- dry-run 不写
publish/**和state/source-lock.json,但可以输出临时报告。
9. SourceLock 更新规则
| 场景 | 是否更新 SourceLock |
|---|---|
| source 完全成功,且无冲突 | 是。 |
| source 部分文件失败 | 否,或只更新明确成功且可安全分片的文件;MVP 建议 source 级不更新。 |
| source 不可用 | 否。 |
| 配置错误 | 否。 |
仅 keep / keep_local | 可更新时间戳和 commit,但不改变 file hash。 |
deletePolicy=report-only | 不移除 lock 文件项,直到删除被人工确认。 |
SourceLock 更新必须与候选 publish/** 写入保持一致:不能出现 lock 标记成功但目标文件 未进入 PR 的情况。
10. 删除策略
| 策略 | 行为 | 风险控制 |
|---|---|---|
report-only | 只报告来源删除,不改 publish/** | MVP 默认。 |
sync-if-unmodified | 如果 C=A,可生成删除候选 | 需要 ReviewBridge 阻断性提示。 |
never | 忽略来源删除,只保留 lock 信息 | 适合来源不稳定项目。 |
删除候选必须进入 Sync PR,不能直接删除 main 上的正式内容。
11. 接口设计
| 接口 | 输入 | 输出 | 说明 |
|---|---|---|---|
runSync | run options | SourceChanges | 执行一次同步。 |
planSource | source config、snapshot、current docs、lock | SyncChange[] | 对单个 source 生成计划。 |
applyPlanToWorkspace | sync plan、write mode | write result | 写候选变更或 dry-run。 |
loadSourceLock | state path | SourceLock 或 empty | 读取同步基线。 |
updateSourceLock | previous lock、successful changes | new lock | 生成新锁。 |
classifyChange | A/B/C | SyncChange | 纯函数动作分类。 |
接口要求:
classifyChange必须无副作用,便于 golden test。runSync必须支持限定 source 运行。- 所有写入通过 WorkspaceStore 完成。
- SourceReader 输出必须先转换为 common path/hash,不向下游泄漏 adapter 私有结构。
12. 状态与持久化
| 文件 | owner | 写入时机 | 说明 |
|---|---|---|---|
state/source-lock.json | SyncEngine | 成功同步后 | source 基线。 |
state/source-changes.json | SyncEngine | 每次同步运行 | 最近一次同步报告。 |
publish/** | SyncEngine 通过 WorkspaceStore 候选写入 | Sync PR 分支 | 正式内容候选变更。 |
如果 ReviewBridge 创建 PR 失败,SourceChanges 仍应保留足够信息供人工恢复。
13. 错误处理
| 错误码 | retryable | 场景 | 处理 |
|---|---|---|---|
sync.source_unavailable | true | source repo/ref 暂不可访问 | 该 source 失败,不更新 lock。 |
sync.source_ref_not_found | false | source ref 不存在 | 阻断该 source。 |
sync.conflict_detected | false | A/B/C 同时变化或目标已存在 | 不写冲突文件,进入报告。 |
sync.delete_conflict | false | 来 源删除但目标被改动 | 不删除,进入报告。 |
sync.lock_update_failed | true | lock 写入失败 | 不标记同步成功。 |
sync.partial_source_failed | true | source 部分文件失败 | report partial,PR 标记 partial。 |
workspace.conflict | false | WorkspaceStore expected hash 不匹配 | 重新读取或人工处理。 |
config.invalid_schema | false | source config 非法 | 阻断 run。 |
错误报告必须包含 sourceId、sourcePath、publishPath、runId;不包含本地绝对路径或 secret。
14. 幂等与重试
- 同一 source snapshot、同一 SourceLock、同一 publish 状态必须生成相同
SourceChanges。 - 重试
runSync不能重复追加相同变更;变更由 sourceId + sourcePath + publishPath + action + hashes 确定。 - source 不可用时重试不得更新 SourceLock。
- lock 更新失败时,下一次运行应重新基于旧 lock 计算,不能假设本次成功。
- 对已经创建的 Sync PR,ReviewBridge 负责 PR 幂等;SyncEngine 只提供稳定 payload。
15. 安全与治理
- 不允许 source 映射写出
publish/**。 - 不允许一个 source 覆盖另一个 source 的 publish path,配置阶段已阻断,运行时仍需防御。
- 冲突不自动解决,避免覆盖人工修订。
- 上游删除默认不删除正式内容。
- SyncEngine 不调用 AgentBridge,避免“同步失败即自动改写”的隐式行为。
16. 测试要求
| 测试 | Fixture | 预期 |
|---|---|---|
| add/update/keep/conflict | fixtures/common/sync/add-update-keep-conflict.json | 输出稳定 SourceChanges。 |
| source delete | fixtures/common/sync/delete-source-page.json | 默认生成 report delete,不直接删除。 |
| config input | fixtures/common/config/sources.single.valid.yaml | 正确读取 enabled source。 |
| duplicate publish path | fixtures/common/config/sources.duplicate-publish-path.invalid.yaml | ConfigManager 阻断,SyncEngine 不运行。 |
| hash normalization | fixtures/common/hash/markdown-lf.md、markdown-crlf.md | 等价内容不产生虚假变更。 |
| concurrent workspace change | fixtures/common/agent/concurrent-change.json | 返回 conflict,不覆盖。 |
模块验收标准:
classifyChange有完整 A/B/C 表驱动测试。SourceChanges输出字段顺序和排序稳定。- failed source 不更新 SourceLock。
- conflict/delete conflict 不写候选内容。
- dry-run 不产生持久化副作用。
17. 设计决策
| 决策 |
|---|