这是本节的多页打印视图。 .
SOW 博客
-
1: 设计归档
- 1.1: 协同发布决策记录
- 1.2: SOW 设计演进:从 Route Graph 收敛到 Repository Core
- 1.3: 发布与恢复
- 1.4: 系统模型
- 1.5: 设计原则
- 1.6: 为什么 SOW 每个仓库只保留一棵包体树
- 1.7: v0.2 重置:用 Plain 与 Managed 取代万能控制面
- 1.8: SOW v0.1 到底证明了什么
- 1.9: SOW v0.1 决策账本:哪些被保留,哪些已退役
- 1.10: SOW v0.1 原始计划:软件仓库的 Git
-
2: 发布注记
- 2.1: SOW v0.5.0
- 2.2: SOW v0.4.0
- 2.3: SOW v0.3.0
- 2.4: SOW v0.2.0
SOW 的发布注记、设计札记与项目动态 —— Pigsty 出品的自包含 APT / YUM 软件仓库管理器。
1 - 设计归档
设计归档解释 SOW 为什么采纳某项契约、否决了哪些替代方案,以及结论实际达到了哪一层证据。
文章按 date 排列,记录设计理由首次形成可维护正文的日期;lastmod 记录后续编辑或与
实现对齐的日期。功能实际在哪个版本交付,仍以发布注记与源码 Tag 为准。
“设计历史”系列把最初的 v0.1 产品计划、现存 44 份 ADR、112 份日期化证据报告,以及 v0.2 重置过程,提炼为少量可持续维护的叙事。原始规划 Prompt、评审与执行记录仍可通过不可变源码 Tag 和 Commit 查阅;它们是第一手史料,不是与本站并行的第二套文档目录。
权威与证据
本专栏是持续维护的设计理由与决策历史权威来源。当前命令、配置与运维行为仍以 SOW 文档为准;历史版本的精确行为则绑定对应源码 Tag 与发布注记。
每项运维结论都应与它真正达到的证据层一致:
前一层通过,不代表后一层自动通过。平台与集成 记录自动化客户端、Provider 与文件系统覆盖;Release 制品属于独立交付门禁。
1.1 - 协同发布决策记录
本记录的早期版本曾提议采用 rclone Executor、publish --dry-run 与独立
sow audit 命令。这些表面均未进入 0.4.0:SOW 不依赖 rclone,publish 没有
--dry-run,sow audit 也不是有效命令。
本页保留决策边界,避免历史提案被误解为当前产品。规范行为以
sow publish与
发布与恢复为准。
最终决策
SOW 0.4.0 保留原生 filesystem 与 r2 发布 Provider。SOW 同时拥有 Repository 语义,以及
保持这些语义所需的传输操作:
- 冻结 Generation 与精确 Change Plan;
- 只创建的 Immutable Object 与 Conditional Mutable Write;
- 持久 Commit Intent 与确定性 Pointer 顺序;
- Attempt、Checkpoint、Inventory、Grace 与 Recovery Evidence;
- Provider 校验与 Canonical Public Visibility Check。
公开承诺保持克制:发布顺序确定、可重启,并最终收敛到一个冻结 Generation;但它不是覆盖所有 Object Key 的原子事务,也不创建 DNS、Bucket Policy、CDN 配置或客户端信任策略。
为什么没有采用 Executor 提案
通用 Bulk-copy Executor 会增加另一个需要版本化的依赖,同时削弱 Plan 中单个 Object 与其 Conditional-write Receipt 的一一对应关系。对于无法保留 SOW 逐对象 SHA-256 Metadata 的批量工具, 还需要引入第二套 Checkpoint Identity 模型。
0.4 因而选择强化既有、更小的传输边界:有界条件式 Multipart Upload、分阶段 Header 与 Idle Progress Deadline、可重放安全操作的重试、精确 Changed-closure Verification,以及共用的 Public HTTP Verifier。这样保留了现有 Checkpoint 模型,也避免把传输迁移伪装成普通升级。
0.4.0 实际交付
原生增量发布
publish 计算精确 Generation Delta,在 Pointer 之前写入 Payload 与校验和寻址 Metadata,持久化
Commit Intent,按确定顺序推进 View,验证 Provider/Public Evidence,并记录 Applied Checkpoint。
Target 已经是当前代时返回幂等 No-op。
操作者确认的 Rebind
publish TARGET --rebind 可修改 Target Name、public_endpoint 或 max_cache_ttl,同时保留稳定
Storage/Target Identity。Storage Endpoint、Provider、Region、Bucket、Repository 与 Prefix 变化
都必须配置新 Target。每次接受的 Rebind 都追加不可变 Audit Revision,并保留 Active
Commit-intent Attempt 的前滚恢复。
强化公共可见性
Filesystem 与 R2 HTTP(S) 目标共用 Canonical-GET Verification、陈旧内容的 Cache-TTL 处理、短有界 Transient Retry、独立 Header/Body-idle Deadline 与 Oversize Guard。Filesystem 条件删除还会等待 Canonical Public 404/410 Evidence。R2 删除仍保持禁用、只报告候选。
操作者流程
changes 是只读的本地 Generation Delta,不是远端 Dry Run。publish 只接受已配置 Target,并由
自身完成远端 Preflight。机器可读结果使用 --json。
报告配置漂移时,应先核对字段。只有允许的可变修正才使用 --rebind;Storage 或 Prefix 变化
必须创建新 Target。
恢复决策
重复原命令就是 Resume 操作,不存在 --resume。Attempt、Checkpoint、Provider 或 Public Evidence
互相矛盾时失败关闭,不会选择一段看起来方便的历史。
明确边界
- 没有全前缀远端
sow audit命令。sow check证明本地 Repository;普通发布证明精确 Changed Closure 与受影响的 Public Pointer。 - R2 Target GC 记录精确、只报告的候选,绝不删除远端对象。
- 多独立写者、分布式锁、自动 Cache Purge、DNS 管理与任意 Executor Plugin 不属于当前契约。
- Provider 写入成功不代表包管理器验收;部署中的 dnf/APT Client 与签名策略是独立 Release Gate。
参考资料
1.2 - SOW 设计演进:从 Route Graph 收敛到 Repository Core
本文对应 2026-08-10 围绕 Repository Core 完成的 SOW 收敛决策。它从早期规划、ADR、评审、 迁移与验证目录中提炼值得维护的结论,但不会把每份过程制品都变成另一套产品文档。
部分封存目录使用“v0.2”“v0.3”表示连续的发布前设计线;下文版本号只指正式 Git Tag。 C2 硬链接开发线在 v0.2.0 打 Tag 之前已经被替换。
更详细的历史由四篇配套记录承载:
- v0.1 原始产品计划解释最初的产品野心,以及控制面为何变得过重;
- v0.1 决策账本逐项标注现存 ADR 后来的命运;
- v0.1 到底证明了什么汇总日期化证据,而不把测试流水账变成维护文档;
- v0.2 重置记录 Plain/Managed 产品边界,以及正式 Tag 前从 C2 修正为单包体布局的过程。
简要历史
| 版本线 | 设计中心 | 历史状态 |
|---|---|---|
| v0.1 | Git/CAS 所有权、Route-aware Materialization、Edge 鉴权、Provider 工作流与 Pigsty 迁移 | 已发布 v0.1.0;后续被取代 |
| v0.2 | 本地 Plain/Managed 引擎、Repository Scope 单包体树、纯元数据视图、Retained Generation 与 Target Scope 发布 | 已发布 v0.2.0;公共布局延续至今 |
| v0.3 | 移除 V1、Package Facts 缓存、Plain 单遍构建与包体有界组提交 | 已发布 v0.3.0;公共布局不变 |
| v0.4 | 单遍校验、独立 RPM Trust Ring、显式 Target Rebind,以及迁移/恢复强化 | 本文更新时的当前发布基线 |
每条已发布版本线的精确代码与陈述,仍保存在 v0.1.0、 v0.2.0、 v0.3.0 与 v0.4.0 源码 Tag 中。
v0.1:问题很有价值,产品概念太多
第一版计划探索了一套宽广的软件分发控制面:Git-backed Canonical State、CAS、Route 与 View、 Materialized Snapshot、远端 Provider、Edge 鉴权、旧系统迁移与证据门禁删除。现存 44 份 ADR(编号至 0045)和庞大的验证树,把许多单点失败模式都写得很清楚。
这些工作留下了可复用的安全思想,但组合后的产品表面过大。太多概念都可能拥有或重新解释同一份 包体与公开路径;在小型本地仓库模型稳定之前,Edge、Provider、Migration 与 Repository 已经 彼此耦合。
因此 v0.2 重置时淘汰了 Git 正典产品状态、独立 CAS 产品层、Route/View/Snapshot 抽象、 把 Edge Entitlement 作为仓库前置条件的设计,以及“用一个模型同时拥有本地生成、远端分发、 旧系统迁移与 CDN 策略”的目标。
v0.2:先建立小而明确的所有权模型
v0.2 有意把产品拆成两条运行路径:
- Plain 扫描调用者拥有的目录,就地重建 RPM 或 DEB 元数据。
- Managed 拥有一个包含独立 Repository 的 Workspace;每个 Repository 都有自己的 Pool、 Dist、SQLite 状态、锁、Operation 与恢复证据。
这次重置建立了沿用至今的持久对象词汇:Package Object、Membership、Desired/Built State、 Generation、Changeset 与显式 Operation Journal。它还确定了稳定锁文件、Pointer-last 文件替换、 Fail-closed 恢复,以及“测试或规格不能自动升级成真实客户端证据”的原则。
开发过程中,C2 布局仍会在每个 RPM View 中创建包体硬链接。封存档案准确记录了这条发布前 设计线,但不能把它重新标注成正式 v0.2.0 Tag 已交付的行为。
v0.2.0:把 Repository Root 变成交付单元
2026-08-05 的单包体决策从 RPM View 中移除了正典包体别名,并在三天后的 v0.2.0 正式交付。
一个 Repository 只有一个正典 pool/ 与纯元数据 dists/;完整根目录才是复制、托管、
鉴权与发布单元。
v0.2.0 同时明确了状态归属:
- Package Object、Desired/Built、Generation、Changeset 与本地 Retention 属于 Repository。
- Publication Attempt、Applied Checkpoint、远端 Inventory、Grace 与删除证据属于一个 Repository 加一个 Target Prefix。
设计明确否决跨 Repository 去重与分布式 Prefix 仲裁。这些不是“还没做的优化”,而是会改变 所有权与失败模型的另一类产品。兼容性导出始终位于正典状态之外。
v0.3:在同一模型上做减法与优化
v0.3 保持 v0.2.0 公共布局不变。它删除退役的 V1 CLI/Runtime 与迁移 Harness,引入
Package Facts 缓存,把 Plain 生成简化为可重建的单遍投影,批量展开 Membership,并用有界
组提交替代逐对象 Promotion。这些变化减少了工作量与代码表面,但没有改变 Repository
pool/ + dists/ 的所有权。
v0.4:强化证据,不再增加一套模型
v0.4 保持 v0.2.0 引入、v0.3 优化的公共布局不变,把重点放在完整性与恢复。Managed 深度校验 收敛成有界单遍契约; RPM Trust 必须来自一条独立验证的 Trust Ring,不能拼接无关密钥的证据;可变 Target 设置只能 通过显式、可审计的 Rebind 修正;Migration、Publication Recovery 与公共交付检查也得到强化。
早期协同发布提案曾考虑把批量传输交给 rclone。最终决策继续使用原生 Provider,因为发布正确性 取决于 Provider Receipt、精确对象身份、提交顺序、Checkpoint State、公共可见性与恢复, 而不只是字节传输。
每次重构都保留下来的原则
v0.1 的产品模型虽然被淘汰,其中真正有用的教训仍然保留:
- 每个持久事实都需要一个明确 Owner;
- 包体与可重建投影不能共享 Authority;
- 不可变包体与元数据先准备,可变 Pointer 最后提交;
- Commit 之后只能前滚恢复,证据矛盾必须 Fail Closed;
- 删除需要同时满足可达性、Grace、所有权与 Provider Capability;
- Path、Client、Mirror、Provider、Release 与 Deployment 属于独立证据门禁;
- 历史 PASS 始终绑定当时的版本、源码与环境。
这些原则如今落在一套更小的对象模型中,详见设计原则、 系统模型、单包体仓库与 发布与恢复。
文档权威边界
现在的维护边界很简单:
- SOW 文档定义当前用户可见的命令、配置、格式与运维行为。
- 本设计专栏定义维护中的设计理由与按日期记录的决策历史。
- 发布注记定义版本级交付与升级声明。
- 源码 Tag 保存精确的历史实现、测试与已被取代的原始文字。
原始规划 Prompt、中间评审、生成的证据摘要与废弃规格仍是历史材料。它们可以解释一个结论如何 形成,但不再是与本站并行的文档权威。
1.3 - 发布与恢复
构建与发布是两个独立状态迁移。构建产生与 target 无关的 Generation;发布把该 Generation 应用到一个供应商前缀,并记录足以在不猜测的前提下恢复的证据。
所有权拆分
| Repository 作用域 | Target prefix 作用域 |
|---|---|
| Package Object | Publication Attempt |
| Desired 与 Built 状态 | Applied Checkpoint |
| Generation 与 Changeset | 远端 inventory |
| retained 包体/元数据引用 | grace 与删除证据 |
这项拆分避免把 filesystem 发布成功当成 R2 的证据,也避免一个 target 的半完成尝试污染另一个。
持久 Target Identity 与 Rebind
Target 有两层嵌套身份:Storage Identity 覆盖 Provider、Endpoint、Region 与 Bucket;Target Identity 再加入公共树 Prefix。首次绑定后,Repository Identity、两层 Target Identity 与三个 Authority Acknowledgement 都不可变。这样配置文件变化时,Attempt、Checkpoint、Grace 与 Maintenance 才不会被错误解释成另一命名空间的证据。
Target Name、public_endpoint 与 max_cache_ttl 只能通过显式
publish --rebind 修改。首次绑定、迁移回填与
每次接受的 Rebind 都追加不可变 Binding Revision。Rebind 与 Publish 使用同一组独占锁,并在
事务内重查不可变字段;Storage 或 Prefix 变化必须使用新 Target。
Target Maintenance 未完成时,TTL 无论增加还是减少都被冻结;Filesystem Conditional-delete
Workflow 还会冻结 public_endpoint。改变当前 TTL 不会重写历史 Grace:删除资格时间取已存
Deadline 与 Applied Checkpoint 加当前 Grace Minimum 中更晚的一个。
发布阶段
提交意图之前,只允许写 add-only object。publish --abort 可以 reconcile 并移除私有 filesystem
stage,但不会删除远端对象。SOW 保留精确的 abandoned-object evidence,让后续尝试可以安全识别
并复用相同字节。
首个可变 APT stable alias 或协议指针写入前,commit intent 必须已经持久化。此后唯一合法恢复 方向是前向。对象存储没有多 key 原子提交,因此 SOW 允许一个有界 mixed-generation 窗口, 并按确定顺序逐个把 view 前滚。
指针顺序
每个 view 都先安装不可变内容;签名伴随文件先于对应的可变指针:
因此客户端一旦看到新指针,就一定能取到它声明的全部对象与签名。
公共验证
日常发布验证精确 Changed Closure 与所有受影响的最终 Pointer,不会从公网重新下载每个未变化 Package。Provider Receipt 与 Public Visibility 是两种独立证明。Filesystem 与 R2 HTTP(S) 目标共用 Canonical-GET Verifier:响应 Header 与 Body Idle 分别计时;Size+1 防止超长响应; 408/425/429/5xx 使用短有界重试;陈旧内容或缺失对象按 Cache TTL 重试。
No-cache 请求只是 Revalidation Hint;只有之后的普通 Canonical GET 看到期望字节,发布才能写入
Checkpoint。Filesystem 条件删除以 HTTP 404/410 证明公共缺失;陈旧 200 会重试至 TTL。
file:// 则使用精确、基于描述符的缺失证明。R2 Public Absence 与远端删除仍保持禁用。
Filesystem 别名会在持久绑定前按 Prospective Canonical Path 检查,包括大小写不敏感卷上的大小写 别名。Backend/Path Preflight 失败不会创建 Binding Row,也不会创建 Target Prefix。
已发布指针栅栏
一个 configured target 已经拥有 Applied Checkpoint 后,本地配置不能静默撤销该 target 仍拥有的 公开 Dist、architecture 或签名指针。操作者必须先退役/解绑 target,或者用新名称和新 prefix 发布替代品。
这样,危险的“配置漏了一项”会变成显式生命周期决策。
不复制包体的保留机制
Retained Generation 保存元数据、manifest 与引用集合,不保存另一棵包体树。Repository-local 可达性包括:
- 当前 Desired/Built Membership;
- retained Generation 引用;
- active operation 与 recovery journal;
- publication grace 与 recovery root。
只有正典 Pool 对象位于完整 closure 之外,且当前文件身份仍与候选记录精确一致时,本地 GC 才允许删除它。
远端删除是一项能力
远端物理删除还要求权威 inventory、target ownership、grace 到期、必要的缓存 absence evidence, 以及原子条件删除 primitive。无法满足这些条件的供应商仍可用于发布,但 SOW 只能报告不可达 候选,不能发出不安全的无条件删除。
r2 Provider 采用这种处理:发布路径已实现,但明确禁用远端物理删除,target GC 只报告候选。
恢复结果
| 持久边界 | 合法结果 |
|---|---|
| 没有 commit intent | reconcile 后放弃或重试 |
| 已有 commit intent | 只能前滚 |
| 已有 Applied Checkpoint | 收敛并进入 grace |
| 证据互相矛盾 | 失败关闭,不虚构状态 |
1.4 - 系统模型
SOW 刻意把模型分层:配置表达意图,数据库记录有主状态,公共树是确定性投影。 任何一层都不能悄悄替代另一层。
对象层级
Workspace
Workspace 提供发现、配置与协调,拥有 sow.yml、私有 .sow/ 目录和稳定锁路径;
它不是包体去重域。
Repository
Repository 是最小的自包含公共归档,也是软件包身份的作用域。它拥有一份 pool/、一组
dists/、一个状态数据库和一条 Generation 序列。即使输入相同,两个 Repository 也不共享
包体与计数器。
Package Object
Package Object 由完成全部包签名后的最终 SHA-256 标识。名称、版本、release 与架构等逻辑坐标 只是元数据,digest 才是字节身份。重复加入同一 digest 是幂等操作;同一正典 Pool 路径出现 不同字节则是硬冲突。
Dist 与 Membership
Dist 是一套 APT 或 RPM 发布策略及其成员集合。Membership 是多对多关系:一个 Package Object
可以属于多个 Dist,但不会因此多出一份正典包体。中性包(all / noarch)会投影到每个
适用架构视图中,逻辑上仍只有一条成员关系。
Desired、Built 与 Generation
Desired 是配置和包操作想要的状态;Built 是最后一次完整渲染并校验通过的公共树。 Generation 是该 Built 状态的不可变清单,Changeset 则是两份清单之间的精确差异。
Desired 与 Built 分离后,中断状态可以被诚实描述:意图可能已经改变,但上一次提交的公共树 仍然有效。
Publication Target
target 把一个 Repository 绑定到供应商 endpoint 与 prefix。发布尝试、已应用 checkpoint、 远端 inventory、grace 与删除证据都属于 target。构建 Repository 与目标无关,发布则不是。
Storage Identity(Provider/Endpoint/Region/Bucket)、Target Identity(Storage 加 Prefix)与 Repository Identity 都是持久身份。只有 Target Name、Public Endpoint 与 Cache TTL 可通过操作者 确认的 Rebind 修改;每次接受的变化都会追加不可变 Binding Revision。
状态流
每一条箭头都受 journal 或事务保护;后一阶段消费前一阶段的不可变身份,不重新解释可变路径。
公共状态与私有状态
| 公共:必须整体复制 | 私有:绝不能对外服务 |
|---|---|
<repo>/pool/ |
sow.yml |
<repo>/dists/ |
.sow/ 数据库与 journal |
| 协议签名与索引 | 锁、stage、恢复前镜像 |
| Generation 描述的常规文件 | 凭据与供应商回执 |
只复制公共树可以得到一个有效的静态仓库,但它不是权威写入者:缺少匹配的私有状态时, 它无法安全续接发布历史、保留策略或垃圾回收。
锁边界
Workspace 生命周期操作取得 Workspace 锁;Repository 变更取得稳定 Repository 锁。 两者都需要时,固定先 Workspace、后 Repository,释放顺序相反。稳定锁路径避免 rename 或恢复 操作在新 inode 上意外形成第二个写入者。
模型假设每个配置 target prefix 只有一个权威 Workspace 且写权限独占;多个独立 Workspace 之间的分布式仲裁不在目标范围内。
1.5 - 设计原则
SOW 首先不是一个“元数据生成器”,而是一个所有权与状态迁移系统,只是它的输出恰好是 APT 与 RPM 仓库。下面这些原则让整套系统保持可推理。
每个持久事实只有一个主人
每类持久事实都只有一个作用域与权威:
| 事实 | 所有者 |
|---|---|
| 包体与软件包身份 | Repository |
| Desired 成员关系与 Built 状态 | Repository |
| Generation 与 Changeset | Repository |
| 发布目标绑定与 Revision | Repository + Target Storage/Prefix |
| 发布尝试与已应用 Checkpoint | Repository + target prefix |
| 远端 inventory、grace 与删除证据 | Repository + target prefix |
状态不会在 Repository 或发布前缀之间暗中共享。因此同一个包可以在不同 Repository 或 target 中各存一份;这是有意为之:本地去重不能制造分布式所有权。
正典数据与可重建投影
Managed 模式下 pool/ 中的包体是正典数据;Plain 模式下,顶层包文件本身就是正典。
协议索引、架构视图、报告与兼容导出都是投影,永远不会成为包体的第二个主人。
耐久规则服从权威来源。Managed 通过有记录的操作移除投影,且只有计算完所有 live / retained owner 的可达性才删除正典数据;Plain 则直接按当前包目录重新生成自有索引路径。
公共树是交付单元
Repository 根由 pool/ + dists/ 组成;整棵树才是静态托管、复制、鉴权与发布单元。
某个 RPM 架构 leaf 是客户端视图,但它不是一个独立拥有状态的 Repository。
sow.yml、.sow/、锁、journal、凭据与恢复文件都是私有状态,绝不能随公共树对外服务。
包体做准备,指针做提交
发布顺序固定为:
包体与不可变元数据可以提前写入而暂不可见;repomd.xml、Release 或 InRelease 这样的
协议指针才是提交边界。只有新指针已经持久化、旧读者与缓存窗口已经关闭后,旧对象才可删除。
恢复成本服从状态成本
Plain 没有需要保存的期望状态历史。它成本最低的正确恢复方式就是重新做一遍内容扫描并覆盖重建, 因此不保存事务 journal,也不花与包体大小成正比的 I/O 去证明上一次尝试。
Managed 状态不同。提交意图之前,如果精确 reconcile 能证明公共指针从未改变,操作可以放弃; 提交意图之后只能前向恢复。Managed 不猜测一次半完成发布“应该已经成功”,而是比较 journal、 manifest、checkpoint、供应商身份与公共树。
证据互相矛盾时操作必须停止。一次可见的拒绝,比仓库历史悄悄分叉更安全。
兼容性是一张矩阵
协议合规、普通客户端、镜像工具、对象存储布局、代理规范化与文件系统语义是不同问题。 SOW 分别记录它们,并用与结论对应的真实客户端或供应商验证。
因此规范 Repository 与导出的 RPM 镜像 leaf 是两种独立产物;其中一种产物的证据不能证明 另一种产物兼容。
证据不会自动升级
规格不是实现,单测不是真实客户端结果,本地 Hugo 构建也不是公网发布。日期化结果始终绑定 当时的源码 revision、环境与版本;布局变化后,必须重跑对应门禁,不能把旧 PASS 直接继承。
非目标让模型保持诚实
当前契约不承诺跨 Repository 去重、重叠多写者、bucket 全局协调、任意第三方镜像工具兼容, 也不在供应商缺少原子条件删除时承诺安全远端删除。这些排除项属于安全契约,不是“尚未补完”。
1.6 - 为什么 SOW 每个仓库只保留一棵包体树
这项决策起草于 2026-08-05 的 v0.2 发布前设计收敛阶段,并在 2026-08-08 随 SOW v0.2.0 正式交付,随后延续为 v0.3 与 v0.4 的布局契约。它记录的是架构选择,不是笼统的兼容性 PASS:协议闭包、普通软件包客户端、镜像工具、静态托管与对象存储始终属于彼此独立的证据层。
最终决策
每个 SOW Repository 只拥有一棵公共树:
一个 Package Object 在一个 Repository 内只有一个正典包体路径。把同一对象加入多个 Dist 或架构视图,只会增加 Membership 与元数据引用,不会再创建一份正典包文件。
完整的 pool/ + dists/ 根目录才是受支持的复制、静态托管、鉴权与发布单元。单个架构
Leaf 只是客户端视图,不是独立拥有状态的仓库。
从 C2 开发线到 v0.2.0
早期发布前 C2 开发线已经把每个包在根 Pool 中存一份,但会在每个 RPM View 内再创建包体
硬链接。这套布局可以让选定的 reposync 客户端得到自包含 Leaf,同时避免在本地复制字节。
问题在于,这种 inode 优化无法穿过完整交付边界:
- 对象存储会把每条路径展开成独立 Key,别名重新变成重复包体;
- 归档与跨文件系统复制可能展开或丢失硬链接身份;
- View 内包路径看起来像正典数据,但所有权其实仍属于 Repository;
- Retention 与 GC 容易误把 link count 当作产品状态;
- 为迁就单个镜像工具,会迫使正典树服从它的本地输出假设。
在 v0.2.0 打 Tag 之前,最终单包体设计已经从正典 RPM View 中删除包体别名。归档中的 C2 文档仍准确描述那条开发线,但不能用来描述正式 v0.2.0 Release Tag。
元数据如何引用 Pool
APT 的 Filename 本来就是相对 Archive Root 的路径,因此软件包条目直接写正典路径:
RPM 则根据实际 View 目录与正典 Pool 路径计算每个 <location href>:
Renderer 与 Checker 复用同一个路径函数:先规范化 Repository-relative Path,再计算字面父级 导航,对数据字节恰好转义一次,基于目录 URI 解析结果,并要求 Round Trip 精确回到原 Pool 路径、且不能离开 Repository。
这样公共树仍然可搬迁:只要 pool/ 与 dists/ 的相对关系不变,同一份字节无需改写
元数据,就可以复制到另一目录、HTTP Origin、Bucket 或 Prefix。
被否决的方案
| 方案 | 为什么不进入正典布局 |
|---|---|
| 每个 RPM View 放包体硬链接或副本 | 制造路径别名与重复对象 Key,把文件系统优化伪装成持久所有权 |
| 包体 Symlink | 无法移植到对象存储,在静态服务、归档与信任边界上也不安全 |
使用 /pool/... 绝对 href |
不同客户端会把它解释成 Host Root 或 Repository-relative,也会破坏非根 Prefix |
绝对 URL 或 xml:base |
把元数据绑定到单一 Origin,无法原样搬迁 |
| 依赖 CDN Rewrite 或 Redirect Object | 把外部 Router 变成仓库正确性的一部分 |
| Workspace 或 Bucket 全局 CAS | 引入 SOW 并不承诺的跨 Repository 所有权、锁、Retention 与删除协调 |
镜像工具兼容属于导出问题
普通软件包客户端只需解析元数据引用并取得包体;镜像工具还可能强制要求每个包都位于它收到的 View 目录之下。这是两种不同契约。
SOW 不会为了覆盖所有镜像工具而扭曲正典 Repository。需要独立 RPM Leaf 时,
sow export rpm-leaf 会在仓库外生成带本地元数据的兼容制品。默认使用 Copy;
--hardlink 只是受控只读边界内、同文件系统上的显式优化。导出目录不是 Generation、
发布源、Membership Owner 或 GC Root。
客户端与镜像工具结果统一记录在兼容性矩阵。
结构化 sow check 成功只能证明路径、摘要与闭包不变式,
不能把尚未运行的客户端或 Provider 单元格自动升级为 PASS。
结果
- Package Identity、Membership、Generation、Retention 与本地 GC 都保持 Repository Scope。
- Publication Attempt、Checkpoint、远端 Inventory、Grace 与删除证据保持 Target-prefix Scope。
- 每个正典文件一对一映射到静态对象 Key。
- 包体删除服从引用闭包与精确文件身份,不依赖 inode link count。
- 不支持只复制某个 RPM 架构 Leaf;应复制 Repository Root 或显式创建 Leaf Export。
1.7 - v0.2 重置:用 Plain 与 Managed 取代万能控制面
归档中的“v0.2”设计线最初使用 C2 View-local RPM Hardlink。这是一条真实实现过的开发线, 但在最终 v0.2.0 Tag 之前已经被单包体设计替换。正式发布的 v0.2.0 已经使用唯一正典 Pool 与纯元数据 View。
归档中仍有数份 v0.2/v0.3 文件声称“v0.2.0”使用 C2 Hardlink;它们写于 Tag 产生之前, 当时的“v0.2.0”指计划中的 Build。实际交付边界由最终 0.2.0 Changelog、Release Note 与源码 Tag 决定,三者都记录唯一正典 Pool 与纯元数据 View。
2026-07-31,v0.1 源码基线刚刚封存,SOW 就开始了一次产品重置。目标不是挽救每个 V1 子系统, 而是找出能够替代 Pigsty 日常软件包构建,同时保留既有安全教训的最小 Repository 产品。
最终形成的边界沿用至今:Plain 负责可重建目录,Managed 负责长期 Repository 所有权。
为什么不在 V1 上逐步删功能
v0.1 把五类不同任务耦合在一起:
- 构建有效 APT/YUM/Asset Repository;
- 维护 Package History 与 Snapshot;
- 迁移庞大的 Pigsty 旧拓扑;
- 发布到多套 Cloud/CDN Target;
- 执行商业 Edge Access Control。
逐项删除功能仍会保留 Git/CAS/Route 所有权模型。重置则直接追问:Repository Engine 真正需要 拥有哪些事实?
最初 Phase One 的边界与功能列表同样重要:
- 不使用 V1 Git Database、Route/Manifest/Quarantine 模型或全局 Content-addressed Product Layer;
- 不提供 Upstream Sync、Pull/Push/Copy/Move/Promote、Edge Entitlement、CDN Bootstrap 或商业拓扑;
- 第一阶段不提供 Remote Publication/State、Object Storage、GC、Snapshot/Freeze、 Managed Export 或 Serving;
- 不提供 Modulemd、Source-package Build、Web UI、跨 Repository 去重或 Multi-writer。
即使 V1 已经为 Upstream Sync 与 Channel Promotion 留下真实 Streaming、Fuzz 与确定性 Proof 证据,重置仍有意把它们移出产品。Package Acquisition 是输入边界,不应成为 Repository Engine 中的第二个 Owner。运维者可以用专门 Mirror/Download 工具准备包集合,再由 SOW 负责 Admission、 Membership、Build 与 Publication。部分 Phase-one 排除项——Publication、Retention、GC 与 External Export——在最终 v0.2.0 Tag 前,以更小的 Repository 所有权模型重新进入。
两条隔离的执行路径
Plain:重建调用者拥有的目录
Plain 模式处理调用者拥有的 RPM/DEB 目录:
它扫描 Package、渲染协议 Metadata、验证结果并最后安装 Pointer。Plain 只拥有生成的 Metadata, 不拥有 Workspace、Repository Database、Generation History 或长期 Membership。
因为 Desired State 就是“当前目录里的 Package”,正确恢复方法是重新运行构建。后续版本进一步 贯彻这一原则,删除 Plain Journal 与重复包体读取。
Managed:拥有生命周期与历史
Managed 模式引入包含多个独立 Repository 的 Workspace:
每个 Repository 拥有自己的 Pool、Package Object、Membership、Desired State、Built Generation、Changeset、Lock、Database 与 Recovery State。不同 Repository 不会暗中去重 或共享计数器。
Dist 是具名 RPM/DEB Membership Set。Architecture View 是 Membership 的投影,不是新的 Owner。
Neutral Package(noarch / all)会进入每个适用架构,但不会复制逻辑 Membership。
哪些决定替换了 V1 概念
| V1 概念 | v0.2 替代项 |
|---|---|
| Git Commit/Ref 正典状态 | Per-Repository SQLite 加显式 Filesystem/Journal Evidence |
| 全局 CAS 与 Route Materialization | Repository-owned Pool 与 Protocol View |
| View/Snapshot/Stable Ref Graph | Dist Membership、Desired Revision、Built Generation、Retained State |
| 一套万能命令面 | 封闭的 Plain 与 Managed CLI 契约 |
| 隐式旧拓扑 | 严格 sow.yml、显式 Name 与 Selector |
| 跨产品 Publication Saga | Repository Build 与 Target Publication 分离 |
| 直接复用 V1 Package | 只复用窄 Parser/Renderer/Version/Signing Port |
最关键的简化是:不借助 Git 或 Edge Router,也能完整解释私有控制状态与公共 Repository Byte。
Desired 与 Built
Managed 模式把中断状态变成显式事实:
- Desired State 是 SQLite 中当前 Package 与 Membership 意图;
- Built Generation 是最近一次完整渲染并验证成功的公共树;
- Changeset 是两份 Built State 的精确差异。
Desired 可以前进,而 Built 继续保持有效。因此 Build 失败时,不需要假装公共树已经变化,也 不需要假装请求从未发生。Operation Journal 与 Status 会显示两者差异。
这个区别在后续所有版本中都保留。
P0 到 P3
实现按依赖顺序推进:
| 阶段 | 契约 |
|---|---|
| P0 | Plain create、真实 RPM/DEB Metadata、签名、Pigsty Filter、Pointer-last Replacement |
| P1 | Workspace/Repository/Dist Lifecycle、严格 Config、Package Identity、Membership 与 Query |
| P2 | Add/Remove/Policy/Build、Journal、Crash Recovery 与 Safe Mutation |
| P3 | Check、Changeset、Handoff、规模、兼容性与 Clean-delivery Evidence |
每个阶段都有 API Contract、Implementation Spec、Adversarial Review 与 Traceability/ Acceptance Record。这些文档在构建 Release 时有用;其中长期有效的决定由本文提炼,不再分别 成为公共权威。
C2 Hardlink 绕路
最初的 Managed RPM 布局在 Root Pool 保存正典文件,并在每个 Architecture View 下创建
Hardlink。Metadata 使用安全的本地 pool/... href。这套 C2 布局让固定的 AlmaLinux 9
reposync 流程通过,而且 Alias 共享 inode,因此本地字节没有重复。
隐藏代价出现在交付边界:
- Object Storage 会把每条 Path 展开成独立完整对象;
- Archive 与跨文件系统 Copy 可能丢失 Hardlink 身份;
- 每个 Dist/Architecture Alias 都看起来像另一份正典 Payload;
- Retention 与 GC 容易依赖 inode/link-count;
- 完整 Repository 无法一对一映射到 Static Object Key。
2026-08-05 的发布前设计快照准确记录了 C2 实现与本地验收,但它不是最终 v0.2.0 Release Contract。
单包体契约起草于 8 月 5 日,8 月 7 日以“next”设计提交,并在 8 月 8 日 Tag 前完成实现。 因此正式 Release 已经交付:
默认 EL reposync 成为已接受兼容性限制;独立 RPM Leaf 改为显式外部 Export。完整理由见
单包体仓库。
重置复用了什么
团队没有从空代码开始,而只保留不携带 V1 所有权假设的窄组件:
- RPM/DEB Parsing 与原生 Version Comparison;
- Metadata Rendering、Compression、Signing 与 Validation;
- Safe Filesystem Primitive;
- 选定 Client Fixture 与 Fault Pattern;
- 精确 Package Identity 与 Provenance Check。
Git State、Route Receipt、Edge Component、旧迁移机制与 Cloud Control Plane 只是参考材料或 删除候选,不是新 Core 的依赖。
Release 证据边界
开发线在 8 月 1 日记录 P0/P1 Acceptance,8 月 2 日记录 P1–P3 Acceptance,8 月 5 日完成 一次本地 Release-gate Run。最终 v0.2.0 源码在单包体实现、Packaging 与 CI 修正进入后,于 8 月 8 日打 Tag。
最终 Release 还交付 Filesystem/R2 Publication、Retained Generation、外部 RPM Leaf Export 与本地 GC;R2 Remote Delete 从一开始就被禁用,Target GC 只保留并报告 Candidate。
这个区别非常重要:
- 8 月 5 日 C2 PASS 只证明产生它的开发快照;
- 8 月 8 日 v0.2.0 Tag 与 Release Note 定义实际交付;
- 后续 v0.3/v0.4 测试不会改写任何一项历史结果。
哪些东西仍在定义 SOW
- Plain 与 Managed 是隔离模式,恢复成本不同;
- Workspace 是 Config/Discovery Scope,Repository 是 Package/History Scope;
- Package Object Identity 不可变,Membership 是逻辑多对多关系;
- Desired 与 Built State 分离;
- 写入使用稳定 Lock、有界 Journal、Pointer-last Install 与 Fail-closed Recovery;
- CLI Syntax、JSON Envelope 与 Exit Class 构成封闭 Automation Contract;
- Protocol、Client、Mirror Tool 与 Provider 兼容性分别报告。
第一手设计快照
- v0.2 开发设计快照 —— PRD、API、C2 Architecture、Review 与本地 Acceptance;
- Single-payload “next” 设计 —— 正式 Tag 前采纳的合同;
- v0.2.0 发布注记与 v0.2.0 源码 Tag —— 实际交付边界。
后续 v0.3 与 v0.4 的强化过程见设计演进。
1.8 - SOW v0.1 到底证明了什么
本页每项结果都只属于当时记录的 v0.1 源码、布局、Fixture 与环境,不能证明当前 SOW 的兼容性或发布就绪。
v0.1 归档包含 112 份日期化 Evidence Report,覆盖 2026-07-11 至 2026-07-31。把它们全部 发布成博客只会降低公共站点的可用性:其中很多是窄故障闭包、重复的 Current-source Audit, 或某个具体实现 Seam 的执行记录。
但直接丢弃也会损失同样重要的信息。这些报告说明:哪些设计决定来自真实客户端与反例,哪些只 达到 Local/Mock 层,以及团队在哪些地方拒绝把不完整证据包装成 PASS。
本文保留的正是这条边界。
证据时间线
| 时间 | 报告数 | 主要工作 |
|---|---|---|
| 2026-07-11 | 3 | 真实 APT/DNF 基线、72,310 包 Manifest 扫描、50k Upstream Streaming |
| 2026-07-12 | 35 | APT/YUM 协议、CAS/GC、发布顺序、恢复、MinIO、性能、迁移、构建/供应链门禁与云验收夹具 |
| 2026-07-13 | 2 | RPM Trust Closure 与 Selected-set Materialization Recovery |
| 2026-07-14–16 | 14 | 旧拓扑、Family Migration、物理所有权、完整副本纳管与包签名 Inventory |
| 2026-07-17–20 | 39 | Cloudflare/R2/COS/Edge Safety、Provider Attestation、Cutover Receipt、配置上限与 Checkpoint-fenced Deletion |
| 2026-07-22 | 10 | Stage、Apply、Prune、Rollback 与 Residue Cleanup 的 Descriptor/Path Identity |
| 2026-07-26–29 | 7 | 递归持久性、Hostile Writer、Replacement Outcome、Preserved Audit 与 Provider 修正 |
| 2026-07-30–31 | 2 | Clean-room APT/YUM/Asset MVP 与最终 Legacy-source Baseline |
哪些结论有较强证据
真实客户端与大仓库
最早的证据没有只验证生成的 XML/Text,而是使用真实 APT 与 DNF,并显式测量规模:
- 真实 APT/DNF 客户端兼容;
- 72,310 包 Manifest 扫描;
- 50k Upstream Streaming;
- 独立的 50k APT、YUM、Materialization、Publish Plan 与 Legacy Adoption 测试。
这些结果支撑了 Streaming 与 Bounded Concurrency 要求,但不证明后续每种布局都自动继承同样 的客户端行为与性能。
协议闭包与负反例
有些设计修正来自负面证明,而不是乐观实现:
- APT Fixed-alias 负 PoC 表明旧客户端语义限制了 Alias 原子性声明;
- YUM Raw-alias Signature-bridge 负 PoC 区分旧 BaseURL 兼容与强 Generation-pinned Metadata;
- YUM Generation Atomicity 精确记录哪些 Pointer 行为能原子化,哪些不能。
延续下来的教训不是 V1 URL Scheme,而是:看似合规的仓库与某个客户端事务,是两个不同结论。
故障与恢复
归档覆盖被中断的 Sync、Publish、Materialize、Snapshot、Archive 与 Remote Restore。后期报告 进一步把文件操作绑定到 Descriptor 与精确 Path Identity,并覆盖 Rollback、Quarantine 与 Preserved Evidence。
代表性记录包括:
- Publish/Recovery 对抗审查;
- Selected-set Recovery Closure;
- State-lock 与 Atomic Publication;
- Derived-state Replacement Outcome。
这也是当前 SOW 区分 Pre-commit Abandon、Post-intent Forward Recovery 与 Contradictory Evidence 的原因,而不是只提供一个笼统的“重试”。
旧系统迁移
迁移证据枚举旧 Make Target、物理布局、Repository Family、Consumer Config、Package-signature Inventory 与 Rollback,并在 Mutation 前使用 Read-only Copy 与精确 Receipt。
这证明了一条特定 Pigsty Cutover Path,却不支持把旧拓扑永久带进产品。最有价值的结果是一套 纪律:把迁移假设变成显式数据与可执行 Gate,切换结束后再让它退役。
Provider 与 Edge 实验
归档包含真实和模拟的 R2、MinIO、Cloudflare、COS 与 Edge 工作。较强报告都明确声明范围:
- 真实 R2 Storage Protocol;
- R2 Publication Storage Transaction;
- Full-provider Checkpoint-fenced Delete;
- Cloudflare Bootstrap、Private Domain、Provider Attestation 与 Rollback 记录。
其中一些使用 Owner 指定的 Nonproduction Resource,另一些是 Offline 或 Mock Validation。 一项真实 R2 结果从未证明 COS 条件替换、EdgeOne Cache 行为或当前 SOW Target State Machine。
归档没有证明什么
即使证据很多,原文仍保留明确开放边界:
- 单个 PoC 不能证明所有 APT、DNF、Proxy、Mirror Tool 或 Provider;
- MinIO S3 兼容不等于 Cloudflare R2 或腾讯 COS 语义;
- Design/Adversarial Review 不是 Implementation Evidence;
- Local/Mock Provider 结果不是生产部署;
- PASS 始终绑定 Source Revision、Fixture、URL Layout 与环境;
- v0.1 证据不能验证后来的 Plain/Managed 或 Single-payload 设计。
这些限制是归档的优点,不应在整理历史时被“润色”掉。
哪些做法进入了设计方法
证据计划留下六条长期实践:
- 标明证据层。 Design、Source、Unit/Fault Test、Real Client、Provider、Artifact 与 Production 彼此独立。
- 保留负结果。 一个反例往往比另一个 Happy Path 更能定义安全边界。
- 把 PASS 绑定到不可变输入。 Source Revision、Container/Client Version、Provider、 Route 与 Fixture 都属于结果。
- 把规模作为契约。 大仓库必须测量 Memory、Call Count 与 Concurrency。
- 证据随模型退役。 历史证明可以解释过去,不能静默升级新布局。
- 让构建与供应链检查长期存在。 Toolchain Portability、Dependency Vulnerability、 Static Analysis、Dead-code Closure 与 Reproducible Delivery 是持续 Release Gate,不是一次性清理。
第一手资料
不可变的 v0.1.0 Evidence 目录 保留全部日期化报告。本文是维护中的解释,原文件则是审计轨迹。
继续阅读 v0.2 重置,可以看到这些教训如何在产品模型大幅缩小 后继续存在。
1.9 - SOW v0.1 决策账本:哪些被保留,哪些已退役
v0.1.0 Tag 中现存 44 份 ADR,编号从 0001 到 0045,其中缺少 0006。 本文保留它们的意义,但不会把每份历史实现契约重新发布成当前指南。
即使实现已经退役,ADR 仍有价值:它记录团队拒绝忽略的失败模式、当时选择的边界,以及危险 操作进入执行前必须取得的证据。
下面把每项决策分为四种命运:
| 命运 | 含义 |
|---|---|
| 保留 | 原则以基本相同的边界继续存在于当前 SOW。 |
| 演化 | 问题与安全规则仍在,但所有权或实现已经改变。 |
| 迁移历史 | 决策只服务一次性的 Pigsty/V1 切换,不再是产品契约。 |
| 退役 | 决策属于已经退出当前 SOW 的 Git/CAS/Route/Edge 产品模型。 |
主要决策日期覆盖 2026-07-11 至 2026-07-28;ADR-0035 另有 7 月 29 日与 31 日的后续修订。 多数早期 ADR 在已经指导实现与证据工作的情况下,于 7 月 19 日集中进入 Git。v0.1.0 Tag Tree 已经核对,包含下表所代表的同一组 44 条 ADR Path。其中一条原文包含已废弃基础设施细节,因此 本文有意不提供直接 Deep Link。
核心状态、发布与信任
| ADR | 原始决策 | 后续命运 |
|---|---|---|
| 0001 | Git 是正典状态,SHA-256 CAS 拥有包体,Ref 表达 View/History,发布按 Target Saga 执行。 | 演化。 Git/CAS/Route 所有权退役;单一 Owner、Pointer-last、独立 Target 状态与证据门禁保留。 |
| 0002 | Public Route、不可变 Generation 与 Client/Provider 兼容使用独立验收门。 | 演化。 Route 机制退役;协议、客户端与 Provider 证据仍然分开。 |
| 0003 | Snapshot 需要经过验证的 Generation Copy 与 Inventory-bound Retention。 | 演化。 CopyObject Snapshot Tree 退役;Retained Generation 只保存 Metadata 与 Refset,不复制包体。 |
| 0004 | Edge Token 验证与部署共享一套跨 Provider 版本化契约。 | 退役。 Edge Entitlement 不再属于 SOW Repository Engine。 |
| 0005 | 每个旧 Make Target 明确映射到 SOW、Retire 或 Policy Reject。 | 迁移历史。 映射完成切换使命后退出活动产品。 |
| 0007 | 公共包与 Gated 包共享一个正典仓库,而不是独立 Pro Bucket。 | 退役。 商业 Edge 拓扑退出产品;更一般的单 Owner 原则保留。 |
| 0008 | GC 服从完整 Reachability,破坏性操作需要显式确认。 | 保留。 当前本地/Target GC 仍要求闭包、精确身份、Grace 与能力。 |
| 0009 | Remote Generation 可见后只能前滚恢复,不能虚构回滚。 | 保留。 Commit Intent 仍是 Abandon/Reconcile 与 Forward-only 的边界。 |
| 0010 | Metadata Signing 绑定精确公私钥身份,轮换是 Repository-wide Transition。 | 演化。 当前 Signer Evidence 与 Trust Ring 保留精确身份,不再使用 V1 Ref 拓扑。 |
| 0011 | Remote Delete 必须具备 Target Ownership、Inventory、Checkpoint、Grace 与条件删除证据。 | 保留。 R2 无法满足原子条件删除,因此继续 Report-only。 |
| 0012 | Private Origin、Cache Topology 与精确 Purge Mapping 都是显式部署契约。 | 退役。 CDN/Private-origin 部署不属于当前 SOW。 |
| 0013 | 持久化 Plan 只是需要重新验证的恢复输入,不能盲目重放。 | 保留。 Managed Recovery 仍会重新绑定 Plan、File、Target Identity 与公共证据。 |
| 0014 | RPM Provenance 记录精确 Packet Evidence,并区分 Ingestion Policy 与历史证明。 | 演化。 当前 Package Authentication 与独立 Trust Ring 替代 V1 Receipt 编码。 |
| 0015 | 配置只命名 Target,Published Ref 与 Checkpoint 属于正典状态。 | 保留。 Target-neutral Generation 与 Target-prefix Attempt/Checkpoint 仍是不同 Owner。 |
| 0016 | Cloud Adapter 使用具体、有界 SDK/API 契约,不建立通用 Provider 抽象。 | 演化。 具体 Provider 边界保留,V1 Edge/COS 细节退役。 |
| 0017 | 已接纳 RPM 签名必须在稳定 Repository Keyring 与已打开包体身份上闭合。 | 演化。 v0.4 独立 Multi-ring Verification 强化了同一信任边界。 |
| 0018 | 选定包集合按一个有界本地事务完成 Stage、Validate、Commit 与 Recovery。 | 演化。 V1 Materialization 退役;当前 Plain/Managed Pointer-last Stage 保留事务原则。 |
旧拓扑、兼容性与迁移
| ADR | 原始决策 | 后续命运 |
|---|---|---|
| 0019 | 为旧客户端冻结 EL7 Metadata 格式与压缩行为。 | 迁移历史。 当前平台支持以兼容性文档而非这份 Freeze 为准。 |
| 0020 | 每条旧物理 Path 与 Selector Group 都有明确 Owner。 | 迁移历史。 它消除了切换歧义,但不是当前仓库模型。 |
| 0021 | 以精确冻结证据复现跨 EL yum/infra/{arch} Projection。 |
迁移历史。 这条特殊 Projection 已退出产品。 |
| 0022 | 物理所有权变化不能破坏 Package Repository 历史连续性。 | 演化。 Generation、Retained Ref 与 Migration Journal 在没有 V1 Route 的情况下延续连续性。 |
| 0023 | 正典 Git Object 在接纳和使用前必须重新绑定精确字节。 | 演化。 Git Authority 退役;Descriptor/Path/Digest 身份检查保留。 |
| 0024 | Materialized Route Receipt 是窄化的 Read/Retirement Capability。 | 演化。 Route Receipt 退役;Capability-bound 检查与删除保留。 |
| 0025 | Lock 绑定精确 Process Instance 与稳定 inode,不按时间自动偷锁。 | 演化。 稳定 Lock inode 与拒绝超时偷锁的原则保留,V1 Process-instance Lease 机制未保留。 |
| 0026 | Offline Archive 使用 Durable Intent 与严格 Archive Admission。 | 退役。 Offline Archive Projection 已离开当前范围。 |
| 0027 | 旧包体只有通过 Path、Package 与 Provenance Admission 才进入正典状态。 | 迁移历史。 Adoption 计划结束;严格 Package Admission 以更窄形式保留。 |
| 0028 | DEB 只打开 Metadata 所需的 Control Archive,并拒绝歧义 Container。 | 保留。 窄解析与有界输入仍属于 APT Package Boundary。 |
| 0029 | Client Floor 与 EL8 Freeze 是显式 Owner Policy,不从代码猜测。 | 迁移历史。 当前平台策略由兼容性参考与 Release Note 决定。 |
| 0030 | 旧 YUM Missing Body 只能依据已评审 Blocker-set Digest 修复。 | 迁移历史。 这项窄 Negative Provenance 例外没有变成通用 Ignore。 |
| 0031 | Gated 旧内容需要精确 Checksum Repair 与 Activation Evidence。 | 退役。 Pro Activation 离开范围;Fail-closed Repair 成为通用教训。 |
Provider 与 Edge 控制面
| ADR | 原始决策 | 后续命运 |
|---|---|---|
| 0032 | 只有一个 Owner 明确指定的 Cloudflare 测试 Tuple 可例外进入。 | 退役。 它只是窄化的历史测试例外,从未成为通用部署规则。 |
| 0033 | Read-only Provider Readiness 拥有独立 Registry 与所有权证据。 | 退役。 Provider Bootstrap Registry 已离开当前 SOW。 |
| 0034 | Worker Bootstrap 使用 Lease、两阶段恢复、精确资源与可逆状态。 | 退役。 Cloudflare Deployment 不再由 Repository Engine 负责。 |
| 0035 | 使用前 Attest Provider Identity、Runtime Binding、Log Sink 与 Lease Ownership。 | 退役。 这套 Edge 控制面退出产品。 |
| 0036 | R2 缺少 Conditional DeleteObject,因此允许在确定性 Capability Probe 与多层 Identity/Fence Proof 后,显式启用 checkpoint-fenced 无条件删除降级。 |
演化;降级未继承。 Capability Probe 与 Fail-closed Default 保留,但当前 SOW 禁用 R2 Remote Delete,Target GC 只报告 Candidate。 |
| 0037 | Gated 发布必须在上传机密字节前证明 Edge Denial。 | 演化。 Edge 产品表面退役,暴露前证明鉴权的原则保留。 |
| 0038 | YUM 切换需要绑定 Endpoint、Generation 与 Trust Bytes 的短期 Receipt。 | 迁移历史。 它让危险切换可审计,随后退役。 |
| 0039 | Caret 在 Go、Edge、Log 与 Origin Route 中只有一种规范 URL 拼写。 | 演化。 旧 Route 退役;Encode-once Path Canonicalization 保留。 |
有界配置与派生状态恢复
| ADR | 原始决策 | 后续命运 |
|---|---|---|
| 0040 | 在分配或执行前限制配置基数与展开后的拓扑。 | 保留。 当前 Parser 与 State Wire 仍有显式 Size/Cardinality Limit。 |
| 0041 | 未知 Final Projection Stage 应保留审计,不得猜测删除。 | 演化。 当前 Recovery 仍保留矛盾证据并 Fail Closed。 |
| 0042 | Derived-state Replacement 有明确 Success、Rollback、Preserved 与 Blocked Outcome。 | 保留。 当前 Operation 报告精确 Recovery Outcome,不折叠成简单成败。 |
| 0043 | 文件 Mutation 绑定 Descriptor Identity,并诚实声明 Same-UID Hostile Writer 边界。 | 保留。 Path Safety Fail Closed,但不宣称保护超出 OS Ownership Boundary 的攻击面。 |
| 0044 | Preserved Audit Copy 只能通过精确 Capability 与 Confirmation Token 退役。 | 演化。 V1 命令退役;Capability-bound Deletion 原则保留。 |
| 0045 | Unjournaled Residue 必须有有界分类器,不能静默接纳或删除。 | 保留。 当前恢复仍区分 Known State、Safe Residue 与 Contradictory Evidence。 |
账本背后的共同模式
真正留下来的不是最复杂的 V1 机制,而是机制下面的小型不变式:
- 每个事实只有一个 Owner;
- Mutation 前绑定精确 Identity;
- 不可变内容先准备,Pointer 最后提交;
- Commit Intent 之后只能前向恢复;
- 删除前必须取得完整证据;
- 输入有明确上限,Non-goal 必须诚实;
- 兼容性结论绑定实际测试过的 Client/Provider。
Git Database、Route Graph、Edge Bootstrap 与迁移命令都可以替换,这些不变式不可以。它们如今 维护在设计原则、系统模型与 发布与恢复中。
不可变的 v0.1.0 ADR 目录仍是第一手 资料。下一篇文章继续汇总独立的 v0.1 证据记录。
1.10 - SOW v0.1 原始计划:软件仓库的 Git
SOW 最初并不是一个小型仓库索引器。原始目标是用一个 Go 控制面,替换 Pigsty 中庞大的 Makefile + rclone 工作流,统一管理 APT、YUM 与静态制品,覆盖本地状态、包生命周期、上游 同步、Channel、View、Snapshot、双云发布、CDN 行为、商业访问控制、迁移、恢复与验证。
当时采用的说法是:“制品仓库的 Git”。 这确实回应了一个真实问题:软件分发同时具有 不可变内容、可变名称、历史、晋级、发布与回滚。问题不在这个类比完全错误,而在它诱导 SOW 承担了过大的产品表面。
第一版设计要解决什么
旧系统已经发布了几十条被 APT、DNF、脚本与安装入口依赖的稳定 URL。替代系统既要保持这些 URL,又要提供 Makefile 工作流难以安全表达的能力:
- 每个已接纳包体只有一个身份;
- Desired State 与每朵云实际发布的状态分离;
- Snapshot 与历史保留不能靠猜测哪些文件仍然存活;
- 增量上传与 Purge 成本应与变更集成正比;
- Metadata 准备完成、Pointer 尚未发布时崩溃,系统仍可恢复;
- 上游索引、包签名与迁移例外都要有 Provenance;
- 商业私有对象不能通过公共索引或 Cache Key 泄漏;
- 兼容性必须来自真实 APT/DNF 客户端,而不只是元数据语法正确。
原始计划有意把这些问题视为同一套所有权系统。
v0.1 选择的模型
Git 是正典状态
.sow/state 是由内嵌 Go Git 库操作的普通 Worktree。Manifest、Ref、Provenance、配置摘要、
Target Checkpoint 与安全标签都是 Git 内容;SQLite 只是可以重建的查询投影。
这样,每次状态迁移都有持久历史,一次 Ref 更新可以成为本地 Commit Boundary。代价是: 包管理、发布、迁移与操作意图,都必须进入同一种 Git 形状的权威模型。
CAS 拥有字节
包体进入以 SHA-256 命名的不可变 Pool;公开树通过 Hardlink 指向 Pool,因此能否回收由 Reachability 决定,而不是由文件名决定。这让本地去重与历史保留成本很低,但也要求 Pool 与 物化树位于同一文件系统,并把 Path、Receipt 与 inode 身份变成产品契约的一部分。
Ref 表达产品语义
Repository、View、Snapshot、Stable、History 与每个 Target 的 Remote Ref 描述内容的意义和 保留边界。移动一个可变 View 不会删除 Stable 或历史可达性;删除 View 只改变引用,GC 始终是 另一项受证据约束的操作。
发布是一条 Saga
Cloudflare R2 与腾讯 COS 是相互独立的 Target。每个 Target 分别准备不可变对象、持久化 Commit Intent、移动协议 Pointer、Purge CDN、验证公共可见性,并推进自己的 Checkpoint。 一朵云成功后,不会仅因另一朵云失败而回滚。
当时已经形成了延续至今的顺序:
这套顺序延续至当前 SOW。
Route 与 Edge 属于仓库正确性
公共树保持旧 URL,Generation-aware Route 选择不可变 Metadata;私有对象则位于 Cloudflare Worker 与 EdgeOne 共享的 Edge 契约之后。鉴权必须先于 Origin 访问,Token 不得进入 Origin URL、Cache Key 或日志。
这解决了机密性闭包,却也让 CDN Route、Token Verifier、Provider 部署、日志 Sink 与缓存拓扑 都变成仓库模型的前置条件。
这套设计为什么有吸引力
v0.1 具备不少强性质:
- 每个持久事实都有明确 Owner;
- 不可变字节与可变名称分离;
- 本地与各 Remote Target 的发布状态独立可观测;
- Pointer-last 发布让半完成工作可以恢复;
- GC 必须计算完整 Reachability Closure;
- Plan 与 Receipt 会重新验证,而不是盲目信任;
- Client、Provider 与实现证据彼此独立;
- 旧系统迁移面被当作数据,而不是口口相传的运维知识。
后续产品没有抛弃这些思想,而是在删掉大量承载机制后继续保留它们。
为什么后来必须重置
到七月底,SOW v0.1 已经包含:
- 作为内部数据库协议的 Git Commit 与 Ref;
- 全局 CAS 与 Hardlink Materialization;
- Route、View、Snapshot、Generation、Projection、Receipt 与 Lease;
- APT/YUM/Asset Sync 与旧系统迁移;
- Cloud SDK、Edge Bootstrap、Provider Attestation、Purge、Token 与私有 Origin;
- 44 份现存 ADR 文件和 112 份日期化 Evidence 报告。
每一项都在解决真实失败模式,但组合起来产生了几个问题。
第一,同一个包与公共路径被太多对象“拥有”:它同时是 CAS Object、Manifest Entry、View Member、Materialized Route、Snapshot Reference 与 Remote Target Object。
第二,本地软件仓库生成与 Cloud/Edge 生命周期强耦合。只想构建一个 YUM 或 APT 仓库的用户, 也要承担商业访问控制和分布式发布的概念成本。
第三,迁移计划变成了永久产品子系统。旧拓扑、Make Target、CDN 行为和 Provider 例外在迁移 时很重要,却不应永久进入软件仓库抽象。
最后,在 Hostile Writer 与 Crash 条件下恢复每条派生 Path,需要的 Identity、Journal、 Quarantine 与 Retirement 机制,已经超过核心任务值得承担的复杂度。
Upstream Sync 与 Channel Promotion 也是一次有意的范围裁剪。V1 已为 Package Fetch 留下真实 Streaming、Fuzz、URL 与 Proof-order 证据,但 Acquisition 会引入自己的 Policy、Retry、 Provenance 与 Remote Ownership。重置把包获取改为外部输入问题,让 SOW 可以拥有 Repository Admission 与 Publication,而不必同时成为万能 Mirror Orchestrator。
重置后保留下来的东西
| v0.1 教训 | 当前维护形态 |
|---|---|
| 每个持久事实只有一个 Owner | 系统模型中的 Repository 与 Target-prefix 所有权 |
| 正典数据与投影不同 | 一个 pool/ 加纯元数据 dists/ |
| 包体先准备,Pointer 最后提交 | 发布与恢复 |
| Commit 后只能前向恢复 | Managed Operation 与 Publication Journal |
| Plan 与 Path 都是不可信输入 | 精确身份、Containment 与 Rebind 检查 |
| 删除需要完整证据 | 引用闭包、Grace、Absence 与 Provider Capability |
| 生成的 Server Config 是派生状态 | 完整 Repository Root 是 Hosting Unit,Serving 仍由运维者显式配置 |
| 兼容性是一张矩阵 | 平台与集成 |
| 历史证据不会自动升级 | 日期化 Design 与 Release 记录 |
被淘汰的东西
- Git 产品状态与“SQLite 只是可丢弃缓存”的模型;
- 跨产品 CAS 与 Hardlink 物化层;
- Route/View/Snapshot 公共命令模型;
- 生成的 Nginx Include、Route Receipt 与 V1 Serving 控制状态;
- 内置 Upstream Sync、Promote 与旧 Make Target 迁移;
- 把 Edge Token Entitlement 与私有 Origin 当作仓库前置条件;
- 多云发布控制面与 Provider Bootstrap/Attestation;
- V1 专属 Projection Receipt、Lease、Quarantine 命令与恢复表面。
一些实现思想后来以更小的形式回归,但所有权边界已经改变:当前 SOW 首先是 Repository Engine,而不是试图拥有周边所有问题的软件分发平台。
时间线
| 日期 | 里程碑 |
|---|---|
| 2026-07-11 | Goal、Brainstorm、技术调研、客户端与规模基线 |
| 2026-07-12 | Git/CAS/发布核心契约与首批协议/Provider 证据 |
| 2026-07-13–16 | 包信任、事务、旧拓扑与迁移闭包 |
| 2026-07-17–20 | Provider Bootstrap、Edge 机密性、删除能力与输入上限 |
| 2026-07-22–29 | Path Identity、Hostile Writer、Quarantine 与 Residue Recovery 强化 |
| 2026-07-30–31 | Clean-room MVP 证据与 v0.1.0 源码封存 |
| 2026-08-01 起 | Plain/Managed 重置,并最终移除 V1 Runtime |
第一手资料与后续记录
不可变的 v0.1.0 docs/ 树保留了原始
架构契约、
需求追踪、
迁移材料、ADR 与 Evidence。
这些文件用于解释历史实现,不能覆盖本站维护中的历史叙事或当前产品文档。
2 - 发布注记
SOW 发布注记涵盖功能、性能、正确性、打包与验证,并按版本从新到旧排列。
v0.5.0 为已有 Plain YUM 仓库新增显式发布时间; 此前 Managed 校验与迁移方面的改进见 v0.4.0。
2.1 - SOW v0.5.0
SOW 0.5.0 主要解决已有平面 YUM 仓库接入 SOW 时的兼容问题。核心新增功能是
sow create --metadata-timestamp SECONDS:替换原有元数据生成器时,发布者可以保留合法的
发布时间,兼容已有 EL7 YUM 客户端的缓存。
已有 YUM 仓库的发布时间
EL7 YUM 会比较新 repomd.xml 中最大的元数据时间戳与本地缓存。如果新索引的时间更早,
YUM 可能拒收它并继续使用旧索引。新的 checksum、不同的 revision 或更新的文件修改时间,
都不能替代这项比较。
此前 Plain create 始终写入零时间戳,便于复现输出,但直接接管已有正时间戳的在线仓库时会
遇到上述问题。0.5 增加了显式指定发布时间的入口:
SECONDS 接受 0 到 253402300799 的整数。负数、非整数、溢出或重复参数会作为
用法错误拒绝。该参数只属于 create,不是 Managed build、publish 或 RPM leaf export
的选项。
| 项目 | 0.5 的行为 |
|---|---|
| 默认 data 时间戳 | 仍为 0 |
| 显式时间戳 | 写入 RPM repomd.xml 的三条 data 记录 |
repomd.xml revision |
Plain 模式仍为 0 |
| RPM 字节与包时间 | 此参数不改变它们 |
| 压缩 XML 与 gzip 头 | 只改此参数时保持原字节 |
| DEB 索引 | 不受此参数影响 |
| 重复构建元数据 | 包字节与选项相同,输出相同,空跑不写文件;强制重签属于另外的包修改操作 |
SOW 不会自动读取当前时钟,也不会替调用者维护发布时间。维护脚本应在上传目录外保存历史最大 发布时间;内容不变且现有索引、签名有效时复用它们,真正更新时递增时间。恢复旧包集合也属于 一次新发布,不能把发布时间一起倒退。若此前转换已经把在线索引重置为零,重建历史下限时还要 纳入可信的转换前备份。
repomd.xml 一旦变化,原有分离签名就不再有效。发布者必须重新签名、验签后再对外提供索引。
--sign-with 签署的是 RPM 包,不会签署 repomd.xml。
迁移指南说明完整操作顺序,以及 SOW 命令与外围维护流程
各自负责的工作。
RPM 依赖兼容性
依赖投影继续采用现代 createrepo_c 对事务前、事务后脚本依赖的处理规则。本次增加了这些阶段 单独出现,以及 Cloudberry、Percona、Grafana 软件包中实际阶段组合的回归用例。
这次增加的是测试覆盖,既没有修改依赖解析,也没有退回 createrepo_c 0.20.1 的输出规则。
依赖条目数量或 pre="1" 属性不同,本身不代表依赖丢失。相关上游变更见
createrepo_c PR #427。
仓库与发布修复
本版修复了历史 Dist 删除清理误删重新加入其他 Dist 的包。签名策略与 pool 路径检查前移, 不兼容输入会在提交期望成员前被拒绝;旧程序留下的中断操作有明确的恢复出口,保留已提交的 数据,字节遭篡改时仍然拒绝恢复。
已经发布的 payload 路径不能复用为不同内容,本地 GC 也不解除该约束;发布恢复不会覆盖 不可变 payload。SQLite 的只读快照不再让已经持久化的写入因 checkpoint 受阻而报错。 外部输入允许经过符号链接父目录,输入末级仍保留原有校验。
APT 验签兼容 GnuPG 的末尾换行约定,不忽略其他内容差异。新生成的 GPG 元数据签名统一使用 SHA-256,不受本地摘要偏好影响;新的元数据构建要求签名 key 当前可用。二进制归档与安装包 同时补齐第三方许可声明。
Pool 路径按大小写折叠比较,字节完全相同也不例外,因此仅大小写不同的文件名不会成为第二个 URL。
在大小写不敏感的文件系统上,SOW 还会拒绝仅大小写不同的源目录:工作区内在加入时拒绝,发布到
filesystem target 时在任何上传之前拒绝。并发执行的 export rpm-leaf --hardlink 与 check
不再把未变化的 Pool 字节误报为完整性错误。本地 gc 会忽略只存在于远端的发布清单条目(例如
R2 仅报告模式下保留的 payload)。
sow repo migrate 会报告 Schema 变化,未迁移的 Repository 会给出应执行的命令。退出码 130
只用于命令自身被中断。file:// R2 凭据必须是普通文件;v0.4.0 中省略了非空且成员全被排除的
Dist 的 add 操作,现在可由下一次 build 收尾。
验收外围维护流程
迁移时应针对实际部署的 YUM/DNF 版本与架构,使用已有 metadata 缓存验收。保持 URL 和 repo ID 不变,从旧索引切换到 SOW 索引,再切换到下一次内容更新,检查索引刷新、实际包下载, 以及所配置的包与仓库签名校验。声明包数量或切换次数时,应同时保留命令、源码版本、输入清单 和对应结果,便于复核。
维护脚本保存时钟、复用签名、选择性重签的能力,不属于 sow create 新增的内置功能。
Plain 仍是平面目录的多文件重建流程:不生成旧式 SQLite 元数据或模块流,也不提供整个仓库的
原子切换。
升级与开发者说明
- 0.3 或 0.4 Managed 工作区必须先备份,再显式执行
sow repo migrate。Schema v13 为候选包池路径 增加索引,sow/v3配置与公共pool/ + dists/布局保持不变。随后逐个 build、check, 一次性刷新受影响的 RPM 认证与 APT 元数据契约,之后未变化的 RPM 可复用匹配的 Built 证据。 - 取消操作先记录失败终态,再清理未提交的包字节;Ctrl-C 中断返回 130。空 Dist 全部输入被 策略排除后仍能正常恢复。长读连接可以延后日志裁剪后的空间回收。
- Agent 元数据签名固定时钟,通过每个身份/时间的一次小消息探签选择实际可用子钥。新 APT 发布拒绝弱签名摘要,历史验签与冻结操作恢复仍遵守原有语义。
- 长期维护的文档集中在本站。兼容性与性能检查分别移至
qa/compat/、qa/perf/,共享 RPM 测试夹具移至internal/testdata/;干净源码交付清单同步采用这些路径。 - 源码工具链升级为 Go 1.27.1,依赖采用 x/crypto v0.56.0;发布工作流固定 GoReleaser v2.18.2 及其 action 提交。
- S3 兼容集成测试改为使用按 digest 固定的
pgsty/silo镜像,即 PGSTY 维护的 MinIO 兼容对象存储,因为上游minio/minio镜像已无法从 Docker Hub 获取。 - 0.5.0 暂不支持 RPM v6 包格式的签名,签名工作流请使用 RPM v4 格式包。
- 2026-09-29 的源码
govulncheck未发现可达漏洞。所依赖模块仍包含GO-2026-5932通告,当前代码没有导入其受影响包;剥离后的二进制扫描仍可能报告模块命中。 这不是“模块无任何通告”的声明,最终发布提交仍需重跑扫描。 - 打 tag 前,应针对最终发布版本完成全量 Go 测试、race、静态分析、漏洞与上游来源检查、 确定性源码交付、客户端集成及归档/安装包校验。本地结果不能替代同一提交的 CI 与 Integration 成功记录。
新建平面目录仍可沿用 sow create DIR。对于已有在线 YUM 仓库,请先阅读
迁移已有 YUM 仓库,并在切换维护命令前确认实际二进制的
create --help 中列出了 --metadata-timestamp。
2.2 - SOW v0.4.0
SOW 0.4.0 是一次面向 Managed 仓库的完整性与恢复版本:它明确了深度校验的 I/O 契约, 禁止跨无关密钥拼装 RPM 信任,提供可审计的发布目标可变配置修正路径,并补齐 v0.3 迁移与 发布中断恢复的剩余缺口。
Plain 仓库行为与公共 pool/ + dists/ 布局均未改变。
从 0.3 升级
先停止全部 Workspace 写入并完成备份,再安装 0.4.0;在执行普通读写命令之前,对每个 Repository 运行一次
sow repo migrate REPOSITORY。迁移是显式、单向的;完成后不要再用 SOW 0.3 打开数据库。
Schema v11 修复了 v0.3 的 Dist 生命周期缺陷:过去一个 Dist 变化时,另一个仍 dirty 的 Dist 可能被遗漏,导致 Repository 错误显示为 clean;现在 Repository 状态始终在修改 Dist 的同一事务 中派生。迁移也会修复发布与签名者证据,但不会猜测 v0.3 未记录的历史签名身份:这类身份保持 显式未验证,不能变成 retained trust assertion。
Schema v12 回填只追加的发布目标绑定账本。首次绑定、迁移回填与之后每次操作者确认的 rebind 都会形成彼此独立、不可变的 revision。
每个物理包体只做一次真实性认证
每次 sow check 都会对每个唯一物理包体执行恰好一次权威内容哈希,即使缓存指纹仍然匹配也
不会跳过。证据绑定设备号、inode、size、mtime、ctime,以及真正被读取的文件描述符。同一物理
对象的硬链接可以共享证明;路径替换或并发身份变化会让证据失效并失败关闭。
验证 retained Generation、遍历最终 Manifest 与生成 sow changes 时复用同一份已认证证据。
因此增加 retained Generation 或 Dist 不会放大包体哈希次数。DEB 与无签名 RPM 只需一遍完整
包体流;带签名 RPM 最多再增加一遍从主 Header 到 EOF 的签名流,且与 Dist 或信任环数量无关。
软件包事实也只按本次选中的摘要集合,以有界、确定性的 SQLite 批次读取。64 MiB 包体的暖构建 不读取包体;指纹漂移与 facts 缺失共享同一次权威包体读取,不再分别扫描。
独立 RPM 信任环
RPM 嵌入签名会在每个候选 trust ring 内独立验证:所有可识别签名 packet 必须在同一个 ring 中 通过,且至少一条通过的路径必须认证 payload。这样既保留历史 CentOS v3/v4 签名与有意双签包的 支持,又禁止把不同 ring 的 packet×key 成功项拼成虚假的单 key retained 信任结论。
当前策略 ring、每个 retained 单 key ring 与组合 trusted ring 共用同一遍签名流。增加密钥只会 改变授权结论,不会增加读取包体的次数。
安全重绑定目标与公共验证
sow publish TARGET --rebind 是显式、由操作者确认的修正路径:它允许修正目标的可变配置,
同时保留原存储身份与恢复状态。
可通过 --rebind 修改 |
必须配置新目标 |
|---|---|
| 目标名称 | Repository 身份 |
public_endpoint |
Provider 或存储 endpoint |
max_cache_ttl |
region、bucket 或 prefix |
普通 publish 遇到不一致时会明确提示 --rebind,绝不会静默接受新值。Rebind 与 publish 使用同一
组独占锁,追加一条审计 revision,并可继续前滚处于 commit-intent 的 active attempt。目标维护
未完成时禁止改变 TTL;filesystem 正在进行 conditional-delete 维护时禁止改变公共端点。历史
grace deadline 永远不会被缩短。
Filesystem 与 R2 的 HTTP(S) 公共端点现在共用同一套强化 verifier。响应 Header 与 Body 空闲进度
分别设定 Deadline,因此大对象只要持续前进,即使总传输超过两分钟也可完成。普通 canonical GET
始终是最终权威;no-cache 请求只用于促进 revalidation。陈旧内容与 404 按 max_cache_ttl 等待,
408/425/429/5xx 使用较短的有界重试窗口,超长响应失败关闭。Filesystem 公共缺失必须最终由
canonical 404/410 证明。R2 远端删除仍明确禁用,Target GC 只报告候选。
Filesystem 目标还会在持久绑定之前按 prospective physical path 检查别名,包括大小写不敏感 卷上的大小写别名。预检失败不会留下 binding row,也不会创建目标 prefix。
恢复、CLI 与实现收敛
- 增量发布恢复只接受每个指针的精确旧 Checkpoint 或目标 Generation 字节;未知或第三种身份 仍然失败关闭。
- 日常发布只验证精确 Changed Object Set,不再下载完整 Public Generation。No-op 与 Applied-checkpoint Recovery 复用完整私有 Inventory Evidence;Target GC 只扫描所需协议 Pointer。
- R2 操作采用分阶段 Header/Idle Deadline、有界条件式 Multipart Upload、可重试读写,并在 Complete Multipart 前核对整对象 SHA-256。
- Generation signer row 成为覆盖局部构建、Dist 增删、迁移、Retention 与本地 GC 的精确 Manifest side table。
- 默认重复执行
sow add时,即使 Package Object 已复用,也会收敛先前--skip或配置变更 留下的选中 dirty Dist。 sow rm --check与写操作共享配置门禁,返回精确、完全不写入的预览;结果不再依赖 scratch 文件系统是否同设备。- 所有 Managed 命令现在都有面向人的输出;
--json继续提供稳定sow.cli/v1Envelope,并在 失败时保留已提交的部分结果及诊断性的 check/preview 结果。 - 退役的 APT v1 Builder、External Sort、Empty-Dist 实现与 Git-tracked by-hash ledger 已移除; 当前 Plain/Managed APT 解析与渲染路径仍有覆盖。
验证与制品
Release 门禁覆盖完整 Go 测试、vet、staticcheck、dead-code reachability、漏洞与 RPM Fork
Provenance 检查、Race Test、Linux amd64/arm64 构建,以及上述确定性包体/facts I/O 契约。
Release 二进制使用 Go 1.27.0 从 v0.4.0 源码 Tag 构建。从源码构建现在要求 Go 1.27.0 或更新
版本(0.3 为 1.26.5);AWS SDK、SQLite、压缩与加密依赖同步刷新,质量门禁固定 staticcheck
v0.8.1、deadcode v0.49.0 与 govulncheck v1.7.0。
本次制品包含四个 Linux/macOS 归档、两个 RPM、两个 DEB 与 SHA256SUMS。每个归档包含
sow、README.md、CHANGELOG.md 与 LICENSE;Linux 原生包随二进制安装 Apache-2.0
许可证。
获取发布版本
通过下载页面获取平台对应命令与已验证摘要,或查看
GitHub Release。安装后先运行 sow version,
逐个迁移 v0.3 Repository,再以 sow check 作为发布前门禁。
长期维护的契约见 Managed 工作区、 签名模型与发布与恢复。
2.3 - SOW v0.3.0
SOW 0.3.0 围绕性能、持久性与产品边界完成了一次集中改进:Plain 仓库生成只需一遍包体处理; Managed 仓库消除了逐对象成员查询,复用经过认证的软件包事实,并用有界组提交提升载荷。正式 交付的二进制与发布流水线也统一收敛到 SOW 实际支持的仓库工作流。
Plain:一遍完成包体处理
默认无签名 sow create 路径通过 --jobs 控制并行度,每个 RPM 或 DEB 只做一次哈希与解析,
随后直接利用保留的检查结果渲染元数据。发布前只做最后一次软件包集合与文件 stat 快照检查,
无需再次读取和哈希所有包体,也能拒绝并发输入变更。
Plain 元数据是可以重建的派生状态。实现不会创建操作日志、恢复垃圾区、回滚前镜像,也不会
重复计算包体哈希。进程中断后重新运行 sow create,即可从软件包目录重建元数据。
Pigsty 预处理会保留 RPM Header 中架构为 i386、i486、i586 或 i686 的软件包,
让它们继续进入仓库元数据与 repo_complete 校验清单;DEB i386 与精确匹配 Patroni 3.0.4
的过滤规则仍按预期生效。
Managed:扩展规模,不放松完整性
成员关系表增加了按包体 SHA-256 的反向索引,Desired 与 Built Membership 通过一次有序批量 投影展开,不再为每个对象单独查询。项目基准中,包含 5,000 个对象的 Dist 列表从约 4.1 秒 缩短至 33 毫秒;50,000 个对象从超过十分钟仍未完成,缩短到约 300 毫秒。
以不可变包体 SHA-256 为键的软件包事实缓存可以随时重建。Ingest 对每个新 RPM 或 DEB 完整 认证并解析一次,保留渲染元数据所需、与具体视图无关的事实。构建时批量载入这些事实并在内存中 匹配;记录缺失或损坏时,再从经过认证的包体惰性重建。
对于未改变的 Pool,暖构建使用设备号、inode、size、mtime 与 ctime 指纹校验载荷,包体读取量
降为零。指纹漂移时执行一次权威 SHA-256 校验并修复缓存路径;sow check 仍负责显式的完整
密码学审计。RPM 元数据产物与 DEB 架构索引使用有界 --jobs 并发,Generation Manifest 与
Changeset 行批量写入,最终规范化复用已有描述符快照,不再重新扫描 Pool。
有界提交与可观察构建
Managed 载荷提升采用有界的单写入者组提交。每批最多处理 512 个对象或 1 GiB:先创建公开 Pool 链接并持久化各目标目录,再移除 pending 名称并持久化共享的 pending 目录。崩溃因此只会留下 可恢复的“仅 pending”“精确双链接”或“仅 Pool”状态;两个名称同时持久丢失仍是完整性错误。
Pending 载荷在私有 0700 目录中直接使用最终的 0644 权限,因此提升只涉及命名空间变更。
Pending 源保护记录对象身份,不会在整个构建期间为每个软件包持有一个描述符,描述符占用保持
有界。发布载荷时也会先持久化目标目录项,再解除源名称。
长时间构建会为渲染、载荷提升、Dist 发布、规范化与收尾阶段追加结构化
build_progress 事件。它们可在 sow log 中观察,但不会为每个阶段增加数据库 checkpoint,
因此进度记录不会让本可成功的构建失败。
收敛发布与运行时边界
R2 发布传输只保留 SOW 实际使用的存储原语:list、head、get 与 conditional put。远端 GC
保持只报告、不删除对象的边界。未使用的云控制、CDN、Edge Worker、迁移程序与替代运行时路径
已从活动代码树移除,正式 CLI 只依赖统一的仓库核心;默认 go test ./... 因而覆盖完整的活动
实现。
正确性与发布质量
- 本地 GC 可以安全移除 Generation 中记录的唯一大小写折叠 Pool 别名,这对大小写不敏感的 文件系统尤其重要。路径、大小与摘要仍必须标识同一不可变对象;存在歧义或漂移时拒绝执行。
- 每个归档都包含
LICENSE。RPM 与 DEB 声明 Apache-2.0,并分别把许可证安装到/usr/share/licenses/sow/LICENSE与/usr/share/doc/sow/copyright。 - CI 强制执行格式化、模块整洁、vet、静态分析、死代码检查、性能测试编译、完整测试、竞态 测试、干净交付检查与软件包快照检查。
- 集成门禁覆盖正式二进制的干净环境混合 RPM/DEB 流程、Ubuntu 22.04 上无签名 Plain APT 的 精确安装、AlmaLinux 8/9/10 的 DNF 签名切换探针,以及固定 MinIO 环境中的 S3 条件写操作。
- 正式发布包含 macOS 与 Linux 的 amd64/arm64 归档、两个 Linux 架构的 RPM 与 DEB,以及
SHA256SUMS。
获取发布版本
通过下载页面获取对应平台的安装命令,或在
GitHub Release 查看全部交付物。安装后运行
sow version,确认使用的是选定的二进制。
完整操作契约见 Plain 模式、Managed 模式 与平台与集成。
2.4 - SOW v0.2.0
SOW 0.2.0 是 Pigsty 出品的自包含 RPM/DEB 仓库管理器,Release 资产以单个 Go 可执行文件覆盖 Linux 与 macOS 构建目标。
两种工作模式
Plain 模式 给目录顶层已有的 RPM 与 DEB 文件就地生成索引:
它写入 repodata/、Packages 与 Packages.gz。Plain 模式没有 Workspace、状态数据库、
Generation,也不生成或签署 DEB Release。
Managed 模式 负责包成员关系与完整生命周期:
每个接纳的包体只在 pool/ 下保存一次;RPM 与 APT 客户端视图位于 dists/,并以不可变
Generation 落成。
生命周期控制
Managed 仓库提供:
- 严格的
sow/v3配置与显式成员策略; - RPM 元数据、APT 元数据与可选 RPM 包体签名;
- 低成本
status与作为发布门禁的九层check; - 可恢复的 filesystem 与 R2 发布尝试;
- 显式保留 Generation 与基于可达性的本地 GC;
- 面向拒绝 rpm-md 父级相对路径的消费者的独立
rpm-leaf导出。
规范 Managed 树必须作为完整仓库交付。已配置目标使用 sow publish;其他传输方式应先把
整根复制到离线 staging,再原子切换。不要逐文件更新在线仓库。
兼容性证据
当前测试套件证明:两种格式都能由现行 CLI 在干净环境构建;Ubuntu 22.04 能消费 Plain APT 仓库;AlmaLinux 8/9/10 探针覆盖 RPM 分离签名行为;另有独立 S3 兼容 Provider Fixture。 这些探针本身不能证明完整的现行 Managed DNF/APT 或 R2 CLI 端到端验收。