跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

设计归档

按日期归档 SOW 在所有权、仓库布局、发布、恢复与兼容性边界上的架构决策。

设计归档解释 SOW 为什么采纳某项契约、否决了哪些替代方案,以及结论实际达到了哪一层证据。 文章按 date 排列,记录设计理由首次形成可维护正文的日期;lastmod 记录后续编辑或与 实现对齐的日期。功能实际在哪个版本交付,仍以发布注记与源码 Tag 为准。

“设计历史”系列把最初的 v0.1 产品计划、现存 44 份 ADR、112 份日期化证据报告,以及 v0.2 重置过程,提炼为少量可持续维护的叙事。原始规划 Prompt、评审与执行记录仍可通过不可变源码 Tag 和 Commit 查阅;它们是第一手史料,不是与本站并行的第二套文档目录。

权威与证据

本专栏是持续维护的设计理由与决策历史权威来源。当前命令、配置与运维行为仍以 SOW 文档为准;历史版本的精确行为则绑定对应源码 Tag 与发布注记。

每项运维结论都应与它真正达到的证据层一致:

设计 -> 实现 -> 聚焦测试 -> 真实客户端/Provider 实跑 -> 发布物

前一层通过,不代表后一层自动通过。平台与集成 记录自动化客户端、Provider 与文件系统覆盖;Release 制品属于独立交付门禁。

1 - 协同发布决策记录

SOW v0.4.0 保留原生发布 Provider 的最终决策,以及未交付的早期 rclone 提案边界。
SOW 0.4.0 已完成最终决策

本记录的早期版本曾提议采用 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 删除仍保持禁用、只报告候选。

操作者流程

sow build -r pgsql
sow check -r pgsql
sow changes -r pgsql
sow publish prod

changes 是只读的本地 Generation Delta,不是远端 Dry Run。publish 只接受已配置 Target,并由 自身完成远端 Preflight。机器可读结果使用 --json。

报告配置漂移时,应先核对字段。只有允许的可变修正才使用 --rebind;Storage 或 Prefix 变化 必须创建新 Target。

恢复决策

是否已经记录持久 Commit Intent?
├── 否 -> 重跑 publish,或对账后 publish --abort
└── 是 -> 重跑 publish;前滚是唯一合法方向

重复原命令就是 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。

参考资料

2 - SOW 设计演进:从 Route Graph 收敛到 Repository Core

v0.1 如何退役、v0.2 如何交付单包体 Repository Core,以及 v0.3-v0.4 如何继续强化。

本文对应 2026-08-10 围绕 Repository Core 完成的 SOW 收敛决策。它从早期规划、ADR、评审、 迁移与验证目录中提炼值得维护的结论,但不会把每份过程制品都变成另一套产品文档。

部分封存目录使用“v0.2”“v0.3”表示连续的发布前设计线;下文版本号只指正式 Git Tag。 C2 硬链接开发线在 v0.2.0 打 Tag 之前已经被替换。

更详细的历史由四篇配套记录承载:

简要历史

版本线 设计中心 历史状态
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、中间评审、生成的证据摘要与废弃规格仍是历史材料。它们可以解释一个结论如何 形成,但不再是与本站并行的文档权威。

3 - 发布与恢复

发布、恢复、保留与安全删除仓库对象的 target-scoped 状态机。

构建与发布是两个独立状态迁移。构建产生与 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 中更晚的一个。

发布阶段

plan
  -> 只增不删的包体
  -> 校验和命名的元数据
  -> 持久 commit intent
  -> 按视图逐一写协议指针
  -> Provider 与 Canonical Public Verification
  -> applied checkpoint
  -> grace
  -> 证据门禁删除

提交意图之前,只允许写 add-only object。publish --abort 可以 reconcile 并移除私有 filesystem stage,但不会删除远端对象。SOW 保留精确的 abandoned-object evidence,让后续尝试可以安全识别 并复用相同字节。

首个可变 APT stable alias 或协议指针写入前,commit intent 必须已经持久化。此后唯一合法恢复 方向是前向。对象存储没有多 key 原子提交,因此 SOW 允许一个有界 mixed-generation 窗口, 并按确定顺序逐个把 view 前滚。

指针顺序

每个 view 都先安装不可变内容;签名伴随文件先于对应的可变指针:

RPM: 校验和命名 repodata -> repomd.xml.asc -> repomd.xml
APT: by-hash/direct indexes -> Release.gpg -> InRelease/Release

因此客户端一旦看到新指针,就一定能取到它声明的全部对象与签名。

公共验证

日常发布验证精确 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
证据互相矛盾 失败关闭,不虚构状态

4 - 系统模型

连接软件包、Dist、Generation 与发布目标的对象模型和状态迁移。

SOW 刻意把模型分层:配置表达意图,数据库记录有主状态,公共树是确定性投影。 任何一层都不能悄悄替代另一层。

对象层级

Workspace
├── Repository
│   ├── Package Object
│   ├── Dist
│   │   └── Membership -> Package Object
│   ├── Desired 状态
│   ├── Built Generation
│   └── Retained Generation 引用
└── Publication Target
    └── Repository + provider + prefix

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。

状态流

软件包输入
    -> 检查/签名/哈希
    -> Package Object + Membership
    -> Desired 状态
    -> 渲染并校验
    -> Built Generation + Changeset
    -> target 发布尝试
    -> applied checkpoint

每一条箭头都受 journal 或事务保护;后一阶段消费前一阶段的不可变身份,不重新解释可变路径。

公共状态与私有状态

公共:必须整体复制 私有:绝不能对外服务
<repo>/pool/ sow.yml
<repo>/dists/ .sow/ 数据库与 journal
协议签名与索引 锁、stage、恢复前镜像
Generation 描述的常规文件 凭据与供应商回执

只复制公共树可以得到一个有效的静态仓库,但它不是权威写入者:缺少匹配的私有状态时, 它无法安全续接发布历史、保留策略或垃圾回收。

锁边界

Workspace 生命周期操作取得 Workspace 锁;Repository 变更取得稳定 Repository 锁。 两者都需要时,固定先 Workspace、后 Repository,释放顺序相反。稳定锁路径避免 rename 或恢复 操作在新 inode 上意外形成第二个写入者。

模型假设每个配置 target prefix 只有一个权威 Workspace 且写权限独占;多个独立 Workspace 之间的分布式仲裁不在目标范围内。

5 - 设计原则

SOW 用来约束所有权、派生状态、发布与证据的一组不变式。

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 全局协调、任意第三方镜像工具兼容, 也不在供应商缺少原子条件删除时承诺安全远端删除。这些排除项属于安全契约,不是“尚未补完”。

6 - 为什么 SOW 每个仓库只保留一棵包体树

用唯一正典 Pool 与纯元数据视图,替代 RPM 视图内包体硬链接的设计决策。

这项决策起草于 2026-08-05 的 v0.2 发布前设计收敛阶段,并在 2026-08-08 随 SOW v0.2.0 正式交付,随后延续为 v0.3 与 v0.4 的布局契约。它记录的是架构选择,不是笼统的兼容性 PASS:协议闭包、普通软件包客户端、镜像工具、静态托管与对象存储始终属于彼此独立的证据层。

最终决策

每个 SOW Repository 只拥有一棵公共树:

<repository>/
  pool/                         # 正典包体
  dists/
    <dist>/
      <architecture>/           # 只有协议元数据

一个 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 的路径,因此软件包条目直接写正典路径:

Filename: pool/p/pkg/pkg_1.0_amd64.deb

RPM 则根据实际 View 目录与正典 Pool 路径计算每个 <location href>:

view:    dists/el9/x86_64/
payload: pool/p/pkg/pkg-1.0-1.x86_64.rpm
href:    ../../../pool/p/pkg/pkg-1.0-1.x86_64.rpm

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。

当前实现机制见包池与元数据视图;周边所有权与生命周期契约见 系统模型和发布与恢复。

7 - v0.2 重置:用 Plain 与 Managed 取代万能控制面

SOW 如何用两条小型执行路径、明确所有权、SQLite 状态与仓库核心,替换 v0.1 的 Git/CAS/Edge 平台。
开发设计线不等于发布版本

归档中的“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 把五类不同任务耦合在一起:

  1. 构建有效 APT/YUM/Asset Repository;
  2. 维护 Package History 与 Snapshot;
  3. 迁移庞大的 Pigsty 旧拓扑;
  4. 发布到多套 Cloud/CDN Target;
  5. 执行商业 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 目录:

sow create /srv/repo

它扫描 Package、渲染协议 Metadata、验证结果并最后安装 Pointer。Plain 只拥有生成的 Metadata, 不拥有 Workspace、Repository Database、Generation History 或长期 Membership。

因为 Desired State 就是“当前目录里的 Package”,正确恢复方法是重新运行构建。后续版本进一步 贯彻这一原则,删除 Plain Journal 与重复包体读取。

Managed:拥有生命周期与历史

Managed 模式引入包含多个独立 Repository 的 Workspace:

Workspace
├── sow.yml
├── .sow/                    私有配置、锁、SQLite、Journal
└── <repository>/
    ├── pool/                正典包体
    └── dists/               客户端协议视图

每个 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 时有用;其中长期有效的决定由本文提炼,不再分别 成为公共权威。

最初的 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 已经交付:

<repository>/
  pool/       每个 Package Object 一份正典包体
  dists/      纯元数据 APT/RPM View

默认 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.3 与 v0.4 的强化过程见设计演进。

8 - SOW v0.1 到底证明了什么

整理 v0.1 的 112 份日期化证据报告:真实客户端、规模、故障、迁移与 Provider 结论证明了什么,又从未证明什么。
版本绑定的证据

本页每项结果都只属于当时记录的 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,并显式测量规模:

这些结果支撑了 Streaming 与 Bounded Concurrency 要求,但不证明后续每种布局都自动继承同样 的客户端行为与性能。

协议闭包与负反例

有些设计修正来自负面证明,而不是乐观实现:

延续下来的教训不是 V1 URL Scheme,而是:看似合规的仓库与某个客户端事务,是两个不同结论。

故障与恢复

归档覆盖被中断的 Sync、Publish、Materialize、Snapshot、Archive 与 Remote Restore。后期报告 进一步把文件操作绑定到 Descriptor 与精确 Path Identity,并覆盖 Rollback、Quarantine 与 Preserved Evidence。

代表性记录包括:

这也是当前 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 工作。较强报告都明确声明范围:

其中一些使用 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 设计。

这些限制是归档的优点,不应在整理历史时被“润色”掉。

哪些做法进入了设计方法

证据计划留下六条长期实践:

  1. 标明证据层。 Design、Source、Unit/Fault Test、Real Client、Provider、Artifact 与 Production 彼此独立。
  2. 保留负结果。 一个反例往往比另一个 Happy Path 更能定义安全边界。
  3. 把 PASS 绑定到不可变输入。 Source Revision、Container/Client Version、Provider、 Route 与 Fixture 都属于结果。
  4. 把规模作为契约。 大仓库必须测量 Memory、Call Count 与 Concurrency。
  5. 证据随模型退役。 历史证明可以解释过去,不能静默升级新布局。
  6. 让构建与供应链检查长期存在。 Toolchain Portability、Dependency Vulnerability、 Static Analysis、Dead-code Closure 与 Reproducible Delivery 是持续 Release Gate,不是一次性清理。

这些实践如今写入设计原则与当前 兼容性参考。

第一手资料

不可变的 v0.1.0 Evidence 目录 保留全部日期化报告。本文是维护中的解释,原文件则是审计轨迹。

继续阅读 v0.2 重置,可以看到这些教训如何在产品模型大幅缩小 后继续存在。

9 - SOW v0.1 决策账本:哪些被保留,哪些已退役

逐项整理 v0.1 现存的 44 份 ADR:当时解决什么问题,后来是延续、演化、仅服务迁移,还是随 V1 一同退役。
历史决策账本

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 证据记录。

10 - SOW v0.1 原始计划:软件仓库的 Git

SOW 为什么最初选择 Git/CAS/Route/Edge 控制面,这套模型解决了什么,又为何后来围绕更小的 Repository Core 重置。
历史设计记录

本文描述 2026-07-11 启动、并在 2026-07-31 封存为 v0.1.0 源码基线的原始计划。 Git/CAS/Route/Edge 产品模型已经退役;当前行为以 SOW 文档和维护中的 系统模型为准。

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。 一朵云成功后,不会仅因另一朵云失败而回滚。

当时已经形成了延续至今的顺序:

不可变包体
  -> 不可变元数据
  -> 持久 Commit Intent
  -> 可变协议 Pointer
  -> 公共验证
  -> Checkpoint
  -> Grace
  -> 证据门禁删除

这套顺序延续至当前 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。 这些文件用于解释历史实现,不能覆盖本站维护中的历史叙事或当前产品文档。

接下来可阅读 v0.1 决策账本、 v0.1 证据账本与 v0.2 重置。