编辑器:工作流版本管理

适用版本:ChengOS v0.1.0+ | 最后核对:2026-08-12 | 来源:crates/cheng-core/src/domain/workflow.rscrates/cheng-core/src/workflow_filecrates/cheng-api/src/rest/handlers/workflow_file/mod.rscrates/cheng-common/src/version.rs

ChengOS 里有三样东西都叫「版本」,把它们混在一起会造成真实的困惑:

版本 作用对象 何时变化
工作流版本(u32 单个工作流 你保存一次修改
文件 schema 版本 workflow.json 这个格式 格式本身演进时
ChengOS 发行版本 整个产品 打一个发行 tag 时

本页讲前两个。第三个见升级与回滚

工作流版本与变更备注

每个工作流都带一个从 1 开始的 version 计数器,以及一个可选的 change_note。保存一次修改会让计数器加一;变更备注记录为什么改。

这个计数器是给作者看的元数据,不是快照仓库。ChengOS 不保存可供你从编辑器回滚的历史图修订。如果你需要那个能力,答案是下面的文件契约加上你自己的版本控制系统——而文件契约正是为此设计的。

生命周期状态

与版本相互独立,工作流还有一个状态:

状态 含义
draft(默认) 正在编辑,不打算承接生产流量
active 可执行,正常运行状态
archived 已退役但保留

以及可见性:private(默认)、publicunlistedshared

draft 提升为 active,是编辑器里最接近「发布一个版本」的动作。

工作流文件契约

工作流可以以 workflow.json 文件的形式存在——这是前端写出、LLM 生成器产出、显式导入消费、文件系统监视器读取的那个格式。

{
  "schemaVersion": 1,
  "workflowId": "fead748f-…",
  "name": "my-workflow",
  "description": "…",
  "state": "draft",
  "version": 3,
  "tags": ["generated"],
  "visibility": "private",
  "definition": { "nodes": [], "edges": [], "notes": [], "containers": [] }
}

这份外部契约刻意区别于内部领域结构和 HTTP DTO。有三条规则支配它:

  1. 身份与租户信息绝不从文件中读取。 workspaceIdtenantId、权限、收藏/模板标记、计算出的计数以及时间戳,都来自经过认证的导入上下文和目录布局——而不是来自不可信的文件正文。一个文件无法把自己偷渡进别的工作区。
  2. 未知的顶层键会被忽略。 由旧版服务端导出、仍带着 nodeCountcreatedAtisDraft 的文件也能干净地解码。
  3. workflowId 只对全新文件可选。 省略它,导入器会分配一个;服务端编码出的每个文件都带 id。

正因为身份是可信上下文而非文件内容,workflow.json 可以安全地提交到 Git、在 PR 里评审、在分支之间做 diff。这就是工作流版本历史在实践中的答案。

文件 schema 版本与升级

schemaVersion 是格式自身的版本。目前只有 V1;不带 schemaVersion 的旧式嵌套信封会被按 V1 解码,并在下次编码时规范化。

升级策略才是有意思的地方:

被动的监视器可以解码更旧的受支持版本,但绝不能仅仅因为服务端升级了,就悄悄改写用户已经带版本号的文件。

改写是一个显式操作。upgrade_to_latest 会解码(同时校验图的形状)、按规范重新编码,并报告 from_versionto_version,以及磁盘字节是否真的会变。将来的 V1 → V2 转换会作为显式步骤放在这里,而不是散落成一堆 serde 别名。

因此,升级服务端绝不会在你背后改写工作流文件。在你有意导入或写回之前,Git diff 始终是干净的。

内容哈希

变更检测使用的是规范化内容哈希sha256:…),而不是 version 计数器或字节相等。这个哈希与身份无关:workflowId 被排除在外,所以它描述的是图,而不是某次安装。

正是它让模板同步的三方比较成为可能——见工作流模板——也正因如此,一个 version 字段从不变化的工作流仍能给出正确的「变了/没变」答案。

服务端管理的元数据键记录文件关系:META_FILE_HASHMETA_FILE_SCHEMA_VERSIONMETA_FILE_SOURCEMETA_FILE_MANAGEDMETA_FILE_IMPORTED_AT,以及从目录安装的工作流所带的模板来源键。它们归服务端所有,并会从文件契约中剥离。

导入:同步接口与监视器

有两条路径把文件带进数据库,二者共享同一个导入器:

  • 文件系统监视器异步地拾取工作区根目录下的变化。
  • 导入 API 同步地运行同一个导入器,因此刚写完 workflow.json 的前端或 LLM 能立刻拿到结构化结果,而不必干等。

导入错误是稳定的机器可读码,并映射到 HTTP 状态:

错误码 状态
WORKFLOW_WORKSPACE_CONFLICTWORKFLOW_SOURCE_CONFLICTWORKFLOW_ID_MISMATCHWORKFLOW_FILE_ADOPTION_REQUIRED 409 Conflict
WORKFLOW_NOT_FOUNDWORKSPACE_NOT_FOUNDWORKFLOW_FILE_NOT_FOUND 404 Not Found
IMPORT_INTERNAL_ERROR 500
INVALID_WORKFLOW_FILEUNSUPPORTED_*WORKFLOW_FILE_NOT_STABLEWORKFLOW_FILE_PATH_INVALID 400 Bad Request

WORKFLOW_FILE_NOT_STABLE 值得单说:它表示导入器读取时文件还在被写入。重试即可,别把它当成校验失败。

ChengOS 发行版本

为完整起见:所有 Rust 组件都从 cheng-common 读取同一个发行版本,它在编译期按顺序解析自 CHENGOS_VERSION(由 CI 从不可变的 Git tag 设置)、最近的 VERSION 文件,或工作空间包版本。

组件不得读取自己的 CARGO_PKG_VERSION:那是每个包各自的构建细节,可能与发行身份漂移。规范形式是不带前缀的 SemVer(0.1.0);v 前缀只属于 Git tag 和发行产物名。

一套可行的做法

  1. 生产工作流保持 active 状态;实验性改动在 draft 副本上做。
  2. 每次保存都填 change_note——它是系统唯一保存的逐次保存注解。
  3. 导出 workflow.json 并提交。Git 会给你编辑器没有提供的历史、追溯与回滚。
  4. 想让某个已提交的版本成为运行中的版本时,显式重新导入。

下一步

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容