这是本节的多页打印视图。 .
设计归档
- 1: 协同发布决策记录
- 2: SOW 设计演进:从 Route Graph 收敛到 Repository Core
- 3: 发布与恢复
- 4: 系统模型
- 5: 设计原则
- 6: 为什么 SOW 每个仓库只保留一棵包体树
设计归档解释 SOW 为什么采纳某项契约、否决了哪些替代方案,以及结论实际达到了哪一层证据。
文章按 date 排列,记录设计理由首次形成可维护正文的日期;lastmod 记录后续编辑或与
实现对齐的日期。功能实际在哪个版本交付,仍以发布注记与源码 Tag 为准。
权威与证据
本专栏是持续维护的设计理由与决策历史权威来源。当前命令、配置与运维行为仍以 SOW 文档为准;历史版本的精确行为则绑定对应源码 Tag 与发布注记。
每项运维结论都应与它真正达到的证据层一致:
前一层通过,不代表后一层自动通过。平台与集成 记录自动化客户端、Provider 与文件系统覆盖;Release 制品属于独立交付门禁。
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。
参考资料
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 | 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 鉴权、旧系统迁移与证据门禁删除。四十五份 ADR 和庞大的验证树,把许多单点失败模式都写得很清楚。
这些工作留下了可复用的安全思想,但组合后的产品表面过大。太多概念都可能拥有或重新解释同一份 包体与公开路径;在小型本地仓库模型稳定之前,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 无关的 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 |
| 证据互相矛盾 | 失败关闭,不虚构状态 |
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 之间的分布式仲裁不在目标范围内。
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 全局协调、任意第三方镜像工具兼容, 也不在供应商缺少原子条件删除时承诺安全远端删除。这些排除项属于安全契约,不是“尚未补完”。
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。