Releases · 数据、状态与 API
- Asset
- ADP-DLV-DOC-DOC-0007
- Feature
- releases
- 领域
- delivery-integrations
- 文档状态
- draft
- 文档 Owner
- DOC
- 事实源
- canonical-docs
- 更新
- 2026/10/14
本文承载 Releases PRD 的稳定字段、API 端点、幂等与并发、失败
收敛和审计细节。所有事实按 PLN · 75c4f1c80ac12f1322fe7bac2132d49a875f3291 固定修订核验;未核验项在本页
末尾保留 unknown。
稳定字段与数据 owner
| 字段 | 合同 | 可写 | 校验事实 |
|---|---|---|---|
id | 服务端生成的稳定 UUID,条目身份 | 只读 | 名称、状态、日期与关联变化都不改变身份 |
workspace / project | 从请求上下文与 Project 归属确定 | 只读 | 跨 Workspace 或跨 Project 请求被拒绝 |
name | 必填,去除首尾空白后不得为空;长度上限由字段定义约束 | 可写 | 空白名称返回校验错误;名称不是唯一键 |
description | 可选自由文本,默认空串 | 可写 | 不承载凭据或内网地址 |
changelog | 可选自由文本,默认空串 | 可写 | 面向发布的变更说明,不是自动生成的 CHANGELOG 事实源 |
status | 枚举 unreleased / released / cancelled,默认 unreleased | 可写 | 未知值被拒绝;语义见产品与数据合同 |
tag | 可选字符串;空白输入归一为 null | 可写 | 只是项目标签文本,不解析 SemVer,也不绑定制品 |
target_date | 可选日期 | 可写 | 计划发布日期 |
actual_release_date | 可选日期 | 可写 | 宣布发布后的实际日期 |
lead | 可选用户;必须是当前 Project 的 active 成员 | 可写 | 非本项目成员被拒绝 |
created_by / updated_by | 服务端记录的操作用户 | 只读 | 审计字段 |
created_at / updated_at / deleted_at | 服务端时间戳;删除为软删除 | 只读 | 软删除后普通查询不再返回该行 |
version | 乐观并发令牌,创建时从 1 开始 | 只读 | 更新、删除和关联变更都必须回传当前值 |
linked_milestones | 只读派生字段,返回已关联 Milestone 的 ID、标题与目标日期 | 只读 | 由关联表派生,不是独立存储 |
数据 owner:Plane 拥有 project_releases、project_release_work_items、project_milestone_release_links 与
project_release_mutations 四张表的事实。本文档只保存稳定语义,不复制字段实现正文。
关系与唯一性:
| 关系 | 唯一性约束 | 说明 |
|---|---|---|
| Release × Work Item | 有效关联(未删除)内 release + work_item 唯一 | 取消关联采用软删除,重新加入可恢复同一行 |
| Release × Milestone | 有效关联(未删除)内 milestone + release 唯一 | 双向可读;两侧状态互不影响 |
| 写入 receipt | 有效记录内 project + actor + idempotency_key 唯一 | 幂等键命名空间是 Project 与 actor,而不是单个 Release |
API 端点
全部端点位于 Plane 公共 API 的 workspaces/{slug}/projects/{project_id} 路径下,读请求要求 active 成员资格,
写请求额外要求 Project Admin 角色;未启用 Releases 的 Project 拒绝写请求。
| 方法与路径 | 语义 | 请求要点 |
|---|---|---|
GET .../releases/ | 分页列出当前 Project 的发布条目,可按 status 过滤,按状态、目标日期与 ID 排序 | 分页响应为带 results 数组的对象 |
POST .../releases/ | 创建条目并返回创建结果 | 必须携带幂等键;名称必填 |
GET .../releases/{release_id}/ | 读取单个条目 | 已删除或超出范围返回稳定错误 |
PATCH .../releases/{release_id}/ | 局部更新条目并递增 version | 必须携带幂等键与当前 version |
DELETE .../releases/{release_id}/ | 软删除条目并收敛关联,成功返回无内容 | 必须携带幂等键与当前 version |
GET .../releases/{release_id}/work-items/ | 读取发布范围(含工作项标识与状态名) | 只读 |
POST .../releases/{release_id}/work-items/ | 加入发布范围 | 非空 ID 列表、幂等键与当前 version |
DELETE .../releases/{release_id}/work-items/ | 移出发布范围 | 非空 ID 列表、幂等键与当前 version |
GET .../releases/{release_id}/milestones/ | 读取已关联 Milestone | 只读 |
POST / DELETE .../releases/{release_id}/milestones/ | 关联或解除 Milestone | 非空 ID 列表、幂等键与当前 version |
GET .../milestones/{milestone_id}/releases/ | 从 Milestone 侧读取关联 Release | 只读 |
POST / DELETE .../milestones/{milestone_id}/releases/ | 从 Milestone 侧建立或解除关联 | 必须携带 release_id、一致 ID 集合、幂等键与当前 version |
后端入口:apps/api/plane/api/urls/release.py、apps/api/plane/api/views/release.py、
apps/api/plane/api/serializers/release.py;Milestone 侧的关联读模型在
apps/api/plane/api/serializers/milestone.py。
幂等与并发
- 幂等键:每个写请求都必须携带非空幂等键。服务端按
project+actor+idempotency_key查找 receipt; 命中且请求摘要一致时直接重放首次记录的状态码与响应体。 - 幂等冲突:同一键命中但请求摘要不同(例如改动了载荷、目标 Release 或 ID 集合)时返回稳定冲突错误,且 不修改数据。因此同一 actor 不能在同一个 Project 内把同一键复用于不同请求。
- 请求摘要:服务端对操作类型与规范化载荷计算摘要,关联操作还会纳入加入/移出方向、排序后的 ID 集合、
对象类型、Release ID 与
version。 - 乐观并发:
PATCH、DELETE与关联变更都必须携带当前version;与库中值不一致时返回稳定冲突错误, 不覆盖他人写入。成功变更把version加一;关联请求在没有任何实际变化时不递增版本。 - 锁与事务:写路径在事务内对 Project、Release 与 receipt 行加行锁;先创建 receipt 判定再执行写入,避免 并发重复副作用。
- 唯一约束:有效关联由条件唯一约束保护,重复加入不会产生第二条有效行;并发重复请求由唯一约束与 receipt 共同收敛。
失败收敛与稳定错误码
| 错误码或状态 | 触发条件 | 收敛结果 |
|---|---|---|
invalid_release(400) | 载荷无效、名称空白、缺少幂等键、日期或负责人格式非法 | 不写入;字段级细节随响应的 details 返回 |
project_mismatch(400) | Project 不属于该 Workspace、关联 ID 越界或属于其它 Project、Milestone 路由 ID 集合不一致 | 整批不写入,不产生部分关联 |
version_conflict(409) | 缺少 version 或 version 陈旧 | 不覆盖既有内容,要求重新读取 |
idempotency_conflict(409) | 同一幂等键用于不同请求内容 | 不写入第二次结果 |
feature_disabled(409) | 当前 Project 未启用 Releases | 不写入;启用后同请求可重试 |
release_deleted(410) | 条目已删除或超出范围 | 不返回内容,也不执行写入 |
milestone_deleted(410) | 目标 Milestone 已删除或超出范围 | 不建立关联 |
| 权限拒绝(403) | 非 Project Admin 提交写请求 | 不写入;前端不得只靠隐藏控件 |
| 删除成功(204) | 合法删除 | 条目与关联软删除;重放同键同内容返回 204 |
所有写入都在数据库事务内完成;任一校验失败都必须整体回滚,不得留下部分关联、部分范围或半更新条目。
审计
- 四张表都保留
created_by、updated_by、updated_at、deleted_at等审计字段,删除是软删除而非物理删除。 - 写入 receipt 记录 actor、幂等键、请求摘要、响应与时间戳,可用于重放、排障和写入追溯。
- 边界:当前修订没有为这四张表安装 append-only 或禁止删除的数据库触发器;receipt 是可软删的普通记录, 因此它是重放与排障证据,不是不可篡改审计,也不能作为发布或环境 evidence。
- 需要不可篡改审计结论时,必须先由 Producer 与安全 owner 决定保证级别,再由 PLN owner 在实现中补齐约束。
安全 NFR
| 项 | 要求 |
|---|---|
| 授权 | 读要求 active 成员,写要求 Project Admin;授权在服务端判定 |
| 租户隔离 | 所有查询绑定 Workspace slug 与 Project ID;跨 Project 关联被拒绝 |
| 失败关闭 | 未启用、无权限、对象越界、版本冲突一律拒绝而不是降级写入 |
| 信息最小化 | 读失败不返回条目内容;自由文本字段不得存放凭据、密钥或内网地址 |
| 枚举风险 | 当前 Milestone 路由对“Release 存在于其它 Project”和“Release 不存在”返回不同错误码,理论上可区分对象存在性;该差异列为待确认事项,收敛前不得把它写成已批准合同 |
| 限流 | 端点继承 Plane 公共 API 的认证与限流中间件,本文不新增例外 |
未核验与 unknown
以下内容在固定修订下无法确认,保持 unknown,不得写成稳定合同:
- Release 导出、报表、批量导入能力的 owner 与可用性(当前无相关 API 或 UI 入口,导出由 Workspace 导出 拥有)。
- 状态转换是否被收窄,以及是否需要服务端转换守卫。
- 列表默认页大小、最大页大小与大规模 Project 下的分页表现目标。
- 负责人离职或成员被停用后,
lead字段的自动清理策略。 - 写入 receipt 的保留期、清理策略与是否需要 append-only 保证。
- 条目上是否存在其它未文档化的字段、过滤器或批量端点。