适用版本:ChengOS v0.1.0+ | 最后核对:2026-08-12 | 来源:
crates/cheng-core/src/domain/workflow.rs、crates/cheng-core/src/workflow_file、crates/cheng-api/src/rest/handlers/workflow_file/mod.rs、crates/cheng-common/src/version.rs
ChengOS 里有三样东西都叫「版本」,把它们混在一起会造成真实的困惑:
| 版本 | 作用对象 | 何时变化 |
|---|---|---|
工作流版本(u32) |
单个工作流 | 你保存一次修改 |
| 文件 schema 版本 | workflow.json 这个格式 |
格式本身演进时 |
| ChengOS 发行版本 | 整个产品 | 打一个发行 tag 时 |
本页讲前两个。第三个见升级与回滚。
工作流版本与变更备注
每个工作流都带一个从 1 开始的 version 计数器,以及一个可选的 change_note。保存一次修改会让计数器加一;变更备注记录为什么改。
这个计数器是给作者看的元数据,不是快照仓库。ChengOS 不保存可供你从编辑器回滚的历史图修订。如果你需要那个能力,答案是下面的文件契约加上你自己的版本控制系统——而文件契约正是为此设计的。
生命周期状态
与版本相互独立,工作流还有一个状态:
| 状态 | 含义 |
|---|---|
draft(默认) |
正在编辑,不打算承接生产流量 |
active |
可执行,正常运行状态 |
archived |
已退役但保留 |
以及可见性:private(默认)、public、unlisted 或 shared。
把 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。有三条规则支配它:
- 身份与租户信息绝不从文件中读取。
workspaceId、tenantId、权限、收藏/模板标记、计算出的计数以及时间戳,都来自经过认证的导入上下文和目录布局——而不是来自不可信的文件正文。一个文件无法把自己偷渡进别的工作区。 - 未知的顶层键会被忽略。 由旧版服务端导出、仍带着
nodeCount、createdAt或isDraft的文件也能干净地解码。 workflowId只对全新文件可选。 省略它,导入器会分配一个;服务端编码出的每个文件都带 id。
正因为身份是可信上下文而非文件内容,workflow.json 可以安全地提交到 Git、在 PR 里评审、在分支之间做 diff。这就是工作流版本历史在实践中的答案。
文件 schema 版本与升级
schemaVersion 是格式自身的版本。目前只有 V1;不带 schemaVersion 的旧式嵌套信封会被按 V1 解码,并在下次编码时规范化。
升级策略才是有意思的地方:
被动的监视器可以解码更旧的受支持版本,但绝不能仅仅因为服务端升级了,就悄悄改写用户已经带版本号的文件。
改写是一个显式操作。upgrade_to_latest 会解码(同时校验图的形状)、按规范重新编码,并报告 from_version、to_version,以及磁盘字节是否真的会变。将来的 V1 → V2 转换会作为显式步骤放在这里,而不是散落成一堆 serde 别名。
因此,升级服务端绝不会在你背后改写工作流文件。在你有意导入或写回之前,Git diff 始终是干净的。
内容哈希
变更检测使用的是规范化内容哈希(sha256:…),而不是 version 计数器或字节相等。这个哈希与身份无关:workflowId 被排除在外,所以它描述的是图,而不是某次安装。
正是它让模板同步的三方比较成为可能——见工作流模板——也正因如此,一个 version 字段从不变化的工作流仍能给出正确的「变了/没变」答案。
服务端管理的元数据键记录文件关系:META_FILE_HASH、META_FILE_SCHEMA_VERSION、META_FILE_SOURCE、META_FILE_MANAGED、META_FILE_IMPORTED_AT,以及从目录安装的工作流所带的模板来源键。它们归服务端所有,并会从文件契约中剥离。
导入:同步接口与监视器
有两条路径把文件带进数据库,二者共享同一个导入器:
- 文件系统监视器异步地拾取工作区根目录下的变化。
- 导入 API 同步地运行同一个导入器,因此刚写完
workflow.json的前端或 LLM 能立刻拿到结构化结果,而不必干等。
导入错误是稳定的机器可读码,并映射到 HTTP 状态:
| 错误码 | 状态 |
|---|---|
WORKFLOW_WORKSPACE_CONFLICT、WORKFLOW_SOURCE_CONFLICT、WORKFLOW_ID_MISMATCH、WORKFLOW_FILE_ADOPTION_REQUIRED |
409 Conflict |
WORKFLOW_NOT_FOUND、WORKSPACE_NOT_FOUND、WORKFLOW_FILE_NOT_FOUND |
404 Not Found |
IMPORT_INTERNAL_ERROR |
500 |
INVALID_WORKFLOW_FILE、UNSUPPORTED_*、WORKFLOW_FILE_NOT_STABLE、WORKFLOW_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 和发行产物名。
一套可行的做法
- 生产工作流保持
active状态;实验性改动在draft副本上做。 - 每次保存都填
change_note——它是系统唯一保存的逐次保存注解。 - 导出
workflow.json并提交。Git 会给你编辑器没有提供的历史、追溯与回滚。 - 想让某个已提交的版本成为运行中的版本时,显式重新导入。

暂无评论内容