Skip to content

1 - Design Records

Dated architecture decisions behind SOW’s ownership, repository layout, publication, recovery, and compatibility boundaries.

Design records explain why SOW adopted a contract, what it rejected, and which evidence boundary applies. They are ordered by date, which records when the rationale first took its maintained form; lastmod records later editorial or implementation-alignment updates. Release notes and source tags remain authoritative for when behavior actually shipped.

The Design History records distill the original v0.1 program, its 44 surviving ADR files, 112 dated evidence reports, and the v0.2 reset into a small maintained narrative. Raw planning prompts, reviews, and transcripts stay available through immutable source tags and commits; they are primary sources, not a second documentation hierarchy.

Authority and evidence

This column is the maintained authority for design rationale and decision history. Current commands, configuration, and operator behavior remain in SOW Docs; exact behavior of a historical release remains attached to that release’s source tag and release note.

Each operational claim should match the evidence layer it has actually reached:

design -> implementation -> focused tests -> real client/provider run -> release artifact

Passing one layer does not imply the next. The platform and integration reference records the automated client, Provider, and filesystem coverage; release artifacts are a separate delivery gate.

1.1 - Coordinated Publication Decision

The v0.4.0 decision to keep SOW’s native publication providers, and the earlier rclone proposal that did not ship.
Decision finalized for SOW 0.4.0

An earlier version of this record proposed an rclone executor, publish --dry-run, and a separate sow audit command. None of those surfaces shipped in 0.4.0. SOW has no rclone dependency, publish has no --dry-run, and sow audit is not a command.

This page preserves the decision boundary so a historical proposal cannot be mistaken for the current product. The normative behavior is documented in sow publish and Publication & Recovery.

Decision

SOW 0.4.0 keeps its native filesystem and r2 publication providers. SOW owns both the Repository meaning and the transport operations needed to preserve it:

  • the frozen Generation and exact change plan;
  • create-only immutable objects and conditional mutable writes;
  • durable commit intent and deterministic pointer order;
  • attempt, checkpoint, inventory, grace, and recovery evidence;
  • provider verification and canonical public visibility checks.

The public guarantee remains deliberately narrow: publication is ordered, restartable, and eventually converges to one frozen Generation. It is not an atomic transaction across all object keys, and it does not create DNS, bucket policy, CDN configuration, or client trust policy.

Why the executor proposal was not adopted

A generic bulk-copy executor would introduce another versioned dependency while weakening the one-to-one relationship between a planned object and its conditional-write receipt. It would also require a second checkpoint identity model for providers whose bulk tool cannot preserve SOW’s per-object SHA-256 metadata.

The 0.4 work instead hardened the existing, smaller transport boundary: bounded conditional multipart upload, phase-specific header and idle-progress deadlines, retryable replay-safe operations, exact changed-closure verification, and a shared public HTTP verifier. This preserved the existing checkpoint model and avoided a transport migration disguised as a routine upgrade.

What 0.4.0 shipped

Native incremental publication

publish computes the exact Generation delta, writes payload and checksum-addressed metadata before pointers, persists commit intent, advances views in deterministic order, verifies provider and public evidence, and records an Applied Checkpoint. A current target is an idempotent no-op.

Operator-confirmed rebind

publish TARGET --rebind can revise the target name, public_endpoint, or max_cache_ttl while preserving stable storage and target identities. Storage endpoint, provider, region, bucket, Repository, and prefix require a new target. Every accepted rebind appends an immutable audit revision and retains forward recovery for an active commit-intent attempt.

Hardened public visibility

Filesystem and R2 HTTP(S) targets share canonical-GET verification, cache-TTL handling for stale content, short bounded transient retries, independent header/body-idle deadlines, and an oversize guard. Filesystem conditional deletion additionally waits for canonical public 404/410 evidence. R2 deletion remains disabled and report-only.

Operator workflow

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

changes is the read-only local Generation delta; it is not a remote dry run. publish accepts only a configured target and owns the remote preflight. Use --json for machine-readable results.

If configuration drift is reported, inspect the fields first. Use --rebind only for a permitted mutable correction; create a new target for any storage or prefix change.

Recovery decision

Was durable commit intent recorded?
├── no  -> rerun publish, or reconcile and publish --abort
└── yes -> rerun publish; forward recovery is the only legal direction

Repeating the original command is the resume operation. There is no --resume flag. Contradictory attempt, checkpoint, provider, or public evidence fails closed rather than selecting a convenient history.

Explicit boundaries

  • There is no full-prefix remote sow audit command. sow check proves the local Repository; normal publication proves its exact changed closure and affected public pointers.
  • R2 target GC records exact report-only candidates and never deletes remote objects.
  • Multiple independent writers, distributed locking, automatic cache purge, DNS management, and arbitrary executor plugins are outside the current contract.
  • A successful provider write is not package-manager acceptance. Run the deployed dnf/APT client and signing policy as a separate release gate.

References

1.2 - SOW Design Evolution: From Route Graphs to the Repository Core

How v0.1 was retired, v0.2 shipped the single-payload Repository core, and v0.3-v0.4 hardened it.

This record dates to the 2026-08-10 consolidation of SOW around its Repository core. It distills the useful decisions from the earlier planning, ADR, review, migration, and verification trees without turning every intermediate artifact into maintained product documentation.

Some sealed folders use “v0.2” and “v0.3” for consecutive pre-release design lines. The version labels below refer only to released Git tags: the C2 hardlink line was replaced before v0.2.0 was tagged.

The detailed history is preserved in four companion records:

The short history

Line Design center Historical status
v0.1 Git/CAS ownership, route-aware materialization, edge authorization, provider workflows, and Pigsty migration Released as v0.1.0; superseded
v0.2 Local Plain/Managed engine, Repository-scoped single-payload tree, metadata-only views, retained Generations, and target-scoped publication Released as v0.2.0; public layout remains current
v0.3 V1 removal, package-facts caching, one-pass Plain builds, and bounded payload commits Released as v0.3.0; same public layout
v0.4 Single-pass verification, independent RPM trust rings, explicit target rebind, and migration/recovery hardening Current released baseline when this record was updated

The exact code and claims for each released line remain available in the v0.1.0, v0.2.0, v0.3.0, and v0.4.0 source tags.

v0.1: useful questions, too many product concepts

The first program explored a broad package-distribution control plane: Git-backed canonical state, content-addressed storage, Routes and Views, materialized snapshots, remote providers, edge authorization, legacy migration, and evidence-bound deletion. Forty-four surviving ADRs, numbered through 0045, and a large verification tree made individual failure modes explicit.

That work produced reusable safety ideas, but the combined product surface was too large for the core job. Too many concepts could own or reinterpret the same package bytes and published paths. Edge, provider, migration, and repository concerns were coupled before a small local repository model had been stabilized.

The v0.2 reset therefore retired Git as canonical product state, the separate CAS product layer, route/view/snapshot abstractions, edge entitlement as a repository prerequisite, and the expectation that one design should simultaneously own local generation, remote distribution, legacy migration, and CDN policy.

v0.2: establish a small ownership model

v0.2 deliberately split the product into two execution paths:

  • Plain scans a caller-owned directory and rebuilds RPM or DEB metadata in place.
  • Managed owns a Workspace containing independent Repositories, each with its own Pool, Dists, SQLite state, locks, operations, and recovery evidence.

This reset established the durable object vocabulary still used today: Package Object, Membership, Desired and Built state, Generation, Changeset, and explicit operation journals. It also established stable lock files, pointer-last file replacement, fail-closed recovery, and the rule that tests or specifications do not upgrade themselves into live-client evidence.

During development, the C2 layout still placed package hardlinks in each RPM view. The sealed archive accurately records that pre-release design line; it must not be relabeled as behavior shipped by the v0.2.0 tag.

v0.2.0: make Repository root the delivery unit

The 2026-08-05 single-payload decision removed canonical package aliases from RPM views. It shipped three days later in v0.2.0. One Repository has one canonical pool/ and metadata-only dists/; the complete root is the copy, hosting, authorization, and publication unit.

v0.2.0 also made the state split explicit:

  • Package Object, Desired/Built, Generation, Changeset, and local retention belong to the Repository.
  • Publication Attempt, Applied Checkpoint, remote inventory, grace, and deletion evidence belong to one Repository plus one target prefix.

The design rejected cross-Repository deduplication and distributed prefix arbitration. Those are not missing optimizations: they would change the ownership and failure model. Compatibility exports remain outside canonical state.

v0.3: simplify and optimize the same model

v0.3 kept the v0.2.0 public layout. It removed the retired V1 CLI/runtime and migration harness, introduced the package-facts cache, made Plain generation a rebuildable one-pass projection, bulk-expanded Membership, and replaced per-object promotion with bounded group commits. These changes reduced work and code surface without moving ownership away from Repository pool/ + dists/.

v0.4: harden evidence rather than add another model

v0.4 kept the public layout introduced in v0.2.0 and optimized in v0.3, then concentrated on integrity and recovery. Managed deep verification became a bounded single-pass contract; RPM trust requires one independently verified trust ring instead of evidence assembled across unrelated keys; mutable target settings can be corrected only through an explicit, audited rebind; and migration, publication recovery, and public-delivery checks were tightened.

An early coordinated-publication proposal considered handing bulk transfer to rclone. The final decision kept native providers because publication correctness depends on provider receipts, exact object identity, commit ordering, checkpoint state, public visibility, and recovery — not only byte transport.

What survived every redesign

The useful v0.1 lessons were not discarded with its product model:

  • every durable fact needs one explicit owner;
  • package bytes and rebuildable projections must not share authority;
  • immutable payload and metadata prepare before mutable pointers commit;
  • post-commit recovery moves forward; contradictory evidence fails closed;
  • deletion requires reachability, grace, ownership, and provider capability;
  • path, client, mirror, provider, release, and deployment evidence are separate gates;
  • historical PASS results stay attached to their version, source, and environment.

These principles now live in a much smaller object model. See Design Principles, System Model, Single-Payload Repository, and Publication & Recovery.

Documentation authority

The maintained boundary is now simple:

  • SOW Docs define current user-facing commands, configuration, formats, and operating behavior.
  • This Design column defines maintained rationale and dated decision history.
  • Release Notes define version-level delivery and upgrade claims.
  • Source tags preserve exact historical implementation, tests, and superseded prose.

Raw planning prompts, intermediate reviews, generated evidence summaries, and abandoned specifications remain historical material. They may explain how a conclusion was reached, but they are not parallel documentation authorities.

1.3 - Publication & Recovery

The target-scoped state machine for publishing, recovering, retaining, and safely deleting repository objects.

Building and publishing are separate state transitions. A build produces a target-neutral Generation. Publication applies that Generation to one provider prefix and records enough evidence to recover without guessing.

Ownership split

Repository-scoped Target-prefix-scoped
Package Object Publication Attempt
Desired and Built state Applied Checkpoint
Generation and Changeset Remote inventory
Retained payload/metadata references Grace and deletion evidence

The split prevents a successful filesystem publication from being treated as proof about R2, and prevents one target’s partial attempt from contaminating another target.

Durable target identity and rebind

A target has two nested identities. Storage identity covers provider, endpoint, region, and bucket; target identity adds the public-tree prefix. Repository identity, both target identities, and the three authority acknowledgements are immutable after the first bind. This is what lets attempts, checkpoints, grace, and maintenance survive a configuration-file edit without being reinterpreted as evidence for another namespace.

Target name, public_endpoint, and max_cache_ttl are mutable only through explicit publish --rebind. Initial bind, migration backfill, and every accepted rebind append an immutable binding revision. Rebind holds the same exclusive locks as publish and rechecks immutable fields inside the transaction; changing storage or prefix requires a new target.

Pending target maintenance freezes TTL in both directions. A filesystem conditional-delete workflow also freezes public_endpoint. Changing the current TTL never rewrites historical grace: deletion eligibility is the later of the stored deadline and the applied checkpoint plus the current grace minimum.

Publication phases

plan
  -> create-only payload
  -> checksum-addressed metadata
  -> durable commit intent
  -> protocol pointers, one view at a time
  -> provider and canonical public verification
  -> applied checkpoint
  -> grace
  -> evidence-gated deletion

Before commit intent, only add-only objects may be written. publish --abort may reconcile and remove private filesystem staging, but it does not delete remote objects. Exact abandoned-object evidence is retained so a later attempt can safely recognize and reuse matching bytes.

Commit intent is persisted before the first mutable APT stable alias or protocol pointer. After that point, the only recovery direction is forward. Object stores do not provide a multi-key atomic commit, so SOW permits a bounded mixed-generation window while it rolls individual views forward in a deterministic order.

Pointer order

Within a view, immutable content is installed first. Signature companions are installed before their corresponding mutable pointer. Examples:

RPM: checksum-named repodata -> repomd.xml.asc -> repomd.xml
APT: by-hash/direct indexes -> Release.gpg -> InRelease/Release

A client that sees a new pointer can therefore reach every object and signature it names.

Public verification

Routine publication verifies the exact changed closure and every affected final pointer; it does not publicly download every unchanged package. Provider receipts and public visibility are separate proofs. Filesystem and R2 HTTP(S) targets share a canonical-GET verifier with independent response header and body-idle deadlines, a size-plus-one oversize guard, short bounded retries for transient 408/425/429/5xx responses, and cache-TTL retries for stale content or missing objects.

No-cache requests are revalidation hints only. A later ordinary canonical GET must observe the expected bytes before publication can checkpoint. For filesystem conditional deletion, HTTP 404 or 410 proves public absence; a stale 200 is retried through the TTL. file:// instead uses exact, descriptor-bound absence. R2 public absence and remote deletion remain disabled.

Filesystem aliases are checked on prospective canonical paths before durable bind, including case aliases on case-insensitive volumes. A failed backend/path preflight creates no binding row and no target prefix.

Published-pointer fence

Once a configured target has an Applied Checkpoint, local configuration cannot silently withdraw a public Dist, architecture, or signing pointer that target still owns. The operator must first retire or unbind the target, or publish a replacement under a new name and prefix.

This turns a dangerous omission into an explicit lifecycle decision.

Retention without payload copies

A retained Generation stores metadata, manifests, and reference sets — not another package tree. Repository-local reachability includes:

  • current Desired/Built memberships;
  • retained Generation references;
  • active operation and recovery journals;
  • publication grace and recovery roots.

Local garbage collection may remove a canonical Pool object only when it is outside that complete closure and the exact file identity still matches the recorded candidate.

Remote deletion is a capability

Remote physical deletion additionally requires authoritative inventory, target ownership, grace expiry, cache-absence evidence when applicable, and an atomic conditional delete primitive. A provider that cannot satisfy the primitive can still publish, but SOW must report unreachable candidates rather than issue an unsafe unconditional delete.

The r2 provider is handled this way: publication is implemented, while remote physical deletion is deliberately disabled and target GC remains report-only.

Recovery outcomes

Durable boundary Legal outcome
No commit intent reconcile, then abort or retry
Commit intent present roll forward only
Applied checkpoint present converge and enter grace
Evidence contradicts fail closed; do not invent state

1.4 - System Model

The objects and state transitions that connect packages, distributions, generations, and publication targets.

The model is deliberately layered. Configuration expresses intent, the database records owned state, and the public tree is a deterministic projection. None of those layers may quietly become a substitute for another.

Object hierarchy

Workspace
├── Repository
│   ├── Package Object
│   ├── Dist
│   │   └── Membership -> Package Object
│   ├── Desired state
│   ├── Built Generation
│   └── Retained Generation references
└── Publication Target
    └── Repository + provider + prefix

Workspace

A Workspace supplies discovery, configuration, and coordination. It owns sow.yml, the private .sow/ directory, and stable lock paths. It is not a package deduplication domain.

Repository

A Repository is the smallest self-contained public archive and the unit of package identity. It owns one pool/, one set of dists/, one state database, and one Generation sequence. Two Repositories share no package bytes or counters even when their inputs match.

Package Object

A Package Object is identified by its final SHA-256 after any package signing. Logical coordinates such as name, version, release, and architecture are metadata; the digest is the byte identity. Re-adding the same digest is idempotent. Different bytes at the same canonical pool path are a hard conflict.

Dist and Membership

A Dist is an APT or RPM publication policy and a collection of memberships. Membership is many-to-many: one Package Object may belong to several Dists without acquiring another canonical payload. Neutral packages (all or noarch) project into every matching architecture view but remain one logical membership.

Desired, Built, and Generation

Desired state is what configuration and package operations ask for. Built state is the last fully rendered and validated public tree. A Generation is an immutable inventory of that Built state; a Changeset is the exact difference between two inventories.

Keeping Desired and Built separate lets an interrupted operation be described honestly: the intent may have changed while the last committed public tree remains valid.

Publication Target

A target binds one Repository to a provider endpoint and prefix. Publication attempts, applied checkpoints, remote inventory, grace, and delete evidence are target-scoped. Building a Repository is target-neutral; publishing it is not.

Storage identity (provider/endpoint/region/bucket), target identity (storage plus prefix), and Repository identity are durable. Only target name, public endpoint, and cache TTL may change through operator-confirmed rebind, and every accepted change appends an immutable binding revision.

State flow

package input
    -> inspect/sign/hash
    -> Package Object + Membership
    -> Desired state
    -> render and validate
    -> Built Generation + Changeset
    -> target publication attempt
    -> applied checkpoint

Every arrow is journaled or transactional. A later stage consumes an immutable identity from the previous one rather than reinterpreting mutable paths.

Public and private state

Public, copy as one unit Private, never serve
<repo>/pool/ sow.yml
<repo>/dists/ .sow/ databases and journals
protocol signatures and indexes locks, stages, recovery pre-images
Generation-described regular files credentials and provider receipts

A copy of only the public tree is a valid static repository. It is not an authoritative writer: without the matching private state it cannot safely resume publication history, retention, or garbage collection.

Locking boundary

Workspace lifecycle operations take the Workspace lock. Repository mutations take a stable Repository lock. When both are required, acquisition order is Workspace then Repository, released in reverse. Stable lock paths prevent a rename or recovery operation from accidentally creating a second writer on a new inode.

The model assumes one authoritative Workspace and exclusive write authority for every configured target prefix. Distributed arbitration between independent Workspaces is a non-goal.

1.5 - Design Principles

The invariants SOW uses to keep ownership, generated state, publication, and evidence understandable.

SOW is not primarily a metadata generator. It is an ownership and state-transition system whose output happens to be APT and RPM repositories. The following principles keep that system small enough to reason about.

One owner for every durable fact

Every durable fact has one scope and one authority:

Fact Owner
Package bytes and package identity Repository
Desired memberships and Built state Repository
Generation and Changeset Repository
Publication target binding and revisions Repository + target storage/prefix
Publication attempt and applied checkpoint Repository + target prefix
Remote inventory, grace, and delete evidence Repository + target prefix

State is not silently shared across Repositories or publish prefixes. The same package may therefore exist once in each Repository or target. That is intentional: local deduplication must not create distributed ownership.

Canonical data, rebuildable projections

Managed package bytes in pool/ are canonical data. In Plain mode, the top-level package files are canonical instead. Protocol indexes, architecture views, reports, and compatibility exports are projections; they never become a second owner of package bytes.

The durability rule follows the authority. Managed removes a projection through its recorded operation and removes canonical data only after reachability across every live and retained owner. Plain simply regenerates its owned index paths from the current package directory.

The public tree is the delivery unit

A Repository root contains pool/ + dists/. That complete tree is the unit for static hosting, copying, authorization, and publication. A single RPM architecture leaf is a client view, but it is not an independently owned Repository.

Private state such as sow.yml, .sow/, locks, journals, credentials, and recovery files must never be served as part of that tree.

Pointers commit; payloads prepare

Publication follows this order:

payload -> immutable/checksum-named metadata -> mutable pointer -> grace -> delete

Payload and immutable metadata may arrive before they are visible. A protocol pointer such as repomd.xml, Release, or InRelease is the commit boundary. Nothing is deleted until the new pointer is durable and the old reader/cache window is closed.

Recovery cost follows state cost

Plain has no desired-state history to preserve. Its cheapest correct recovery is a fresh one-pass scan and overwrite rebuild, so it stores no transaction journal and does not spend package-size I/O proving an old attempt.

Managed state is different. Before commit intent, an operation may be abandoned if exact reconciliation proves that no public pointer changed. After commit intent, recovery is forward-only. Managed does not guess whether a half-finished publication “probably worked”; it compares journals, manifests, checkpoints, provider identities, and the public tree.

Contradictory evidence stops the operation. A visible refusal is safer than an invisible fork in repository history.

Compatibility is a matrix

Standards compliance, ordinary client behavior, mirror-tool behavior, object-storage layout, proxy normalization, and filesystem semantics are different questions. SOW records them separately and uses a real client or provider for the claim being made.

The canonical Repository and an exported RPM mirror leaf are therefore separate artifacts; evidence for one does not establish compatibility for the other.

Evidence never upgrades itself

A specification is not implementation. A unit test is not a live-client result. A local Hugo build is not a published site. A dated result remains attached to its source revision, environment, and version; it cannot be reused as a PASS for a later layout without rerunning the relevant gate.

Non-goals keep the model honest

The current contract does not promise cross-Repository deduplication, overlapping writers, bucket-global coordination, arbitrary third-party mirror compatibility, or safe remote deletion on providers without an atomic conditional delete primitive. Excluding these is part of the safety contract, not an unfinished implementation detail.

1.6 - Why SOW Uses One Payload Tree per Repository

The decision to replace RPM view-local package hardlinks with one canonical pool and metadata-only views.

This decision was drafted on 2026-08-05 during the pre-v0.2 release design consolidation and shipped in SOW v0.2.0 on 2026-08-08. It remains the layout contract in v0.3 and v0.4. It records an architecture choice, not a blanket compatibility claim: protocol closure, ordinary package clients, mirror tools, static hosting, and object storage are verified as separate evidence layers.

Decision

Each SOW Repository owns one public tree:

<repository>/
  pool/                         # canonical package bytes
  dists/
    <dist>/
      <architecture>/           # protocol metadata only

A Package Object has one canonical payload path inside one Repository. Adding the same object to several Dists or architecture views adds membership and metadata references; it does not create another canonical package file.

The complete pool/ + dists/ root is the supported copy, static-hosting, authorization, and publication unit. A single architecture leaf is a client view, not an independently owned repository.

From the C2 development layout to v0.2.0

An earlier pre-release C2 development layout stored each package once in a root Pool, then created package hardlinks inside RPM views. This gave selected reposync clients a self-contained leaf without duplicating local bytes.

The local inode optimization did not survive the full delivery model:

  • object storage expands each path into a separate key, so aliases duplicate payloads;
  • archives and cross-filesystem copies can expand or lose hardlink identity;
  • a view-local package path looks canonical even though ownership remains at Repository scope;
  • retention and garbage collection risk treating link count as product state;
  • preserving mirror-tool behavior would force the canonical tree to follow one client’s local-output assumptions.

Before v0.2.0 was tagged, the final single-payload design removed package aliases from canonical RPM views. The archived C2 documents remain accurate for that development line, but they do not describe the v0.2.0 release tag.

How metadata reaches the Pool

APT already treats Filename as an archive-root-relative path, so a package entry names its canonical path directly:

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

For RPM, each <location href> is computed from the actual view directory to the canonical Pool path:

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

The renderer and checker share the same path function. It normalizes Repository-relative paths, computes literal parent navigation, escapes data bytes exactly once, resolves the result against a directory URI, and requires the round trip to land on the original Pool path without leaving the Repository.

This keeps the public tree relocatable. The same bytes can be copied under another filesystem directory, HTTP origin, bucket, or prefix without rewriting metadata, provided the relative relationship between pool/ and dists/ is preserved.

Rejected alternatives

Alternative Why it is not canonical
Package hardlink/copy in every RPM view Creates path aliases and duplicate object keys; makes a filesystem optimization look like durable ownership
Symlinked payloads Not portable to object storage and unsafe across static servers, archives, and trust boundaries
Leading /pool/... href Different clients interpret it as host-root or repository-relative; it also breaks non-root prefixes
Absolute URL or xml:base Binds metadata to one origin and prevents unchanged relocation
CDN rewrite or redirect objects Makes an external router part of repository correctness
Workspace- or bucket-global CAS Introduces cross-Repository ownership, locking, retention, and deletion coordination that SOW does not promise

Mirror-tool compatibility is an export concern

An ordinary package client only needs to resolve metadata references and fetch package bytes. A mirror tool may additionally insist that every package be stored below the view directory it was given. Those are different contracts.

SOW does not distort the canonical Repository to make every mirror tool happy. When an operator needs a standalone RPM leaf, sow export rpm-leaf creates an external compatibility artifact with regenerated leaf-local metadata. Copy is the default; --hardlink is an explicit same-filesystem optimization for a controlled, read-only boundary. The export is not a Generation, publication source, membership owner, or garbage collection root.

Client and mirror-tool results remain in the compatibility matrix. A successful structural sow check proves path, digest, and closure invariants; it does not silently upgrade an unrun client or provider cell to PASS.

Consequences

  • Package identity, membership, Generation, retention, and local GC stay Repository-scoped.
  • Publication attempts, checkpoints, remote inventory, grace, and deletion evidence stay target-prefix-scoped.
  • Every canonical file maps one-to-one to a static object key.
  • Payload deletion follows reference closure and exact identity, never inode link count.
  • Copying only an RPM architecture leaf is unsupported; copy the Repository root or create an explicit leaf export.

The current mechanism is documented in Pool & Metadata Views, while System Model and Publication & Recovery define the surrounding ownership and lifecycle contracts.

1.7 - The v0.2 Reset: Plain and Managed Instead of a Universal Control Plane

How SOW replaced the v0.1 Git/CAS/Edge platform with two small execution modes, explicit ownership, SQLite state, and a repository-focused release.
Development-line versus release version

The archived “v0.2” design line initially used C2 view-local RPM hardlinks. That was a real development implementation, but it was replaced by the single-payload design before the final v0.2.0 tag. The released v0.2.0 layout already used one canonical Pool and metadata-only views.

Several archived v0.2/v0.3 files nevertheless say “v0.2.0” used C2 hardlinks. They were written before the tag existed and used “v0.2.0” for the planned build. The delivered boundary is defined by the final 0.2.0 Changelog, release note, and source tag, all of which record one canonical Pool and metadata-only views.

On 2026-07-31, immediately after sealing the v0.1 source baseline, SOW began a product reset. The objective was not to salvage every V1 subsystem. It was to identify the smallest repository product that could replace Pigsty’s daily package-build workflow while keeping the safety lessons already learned.

The result was a boundary that still defines SOW: Plain for rebuildable directories, and Managed for long-lived repository ownership.

Why reset instead of incrementally simplifying V1

The v0.1 model had coupled five different jobs:

  1. build valid APT/YUM/asset repositories;
  2. maintain package history and snapshots;
  3. migrate a large legacy Pigsty topology;
  4. publish to multiple cloud/CDN targets;
  5. enforce commercial Edge access.

Removing one feature at a time would leave the same Git/CAS/Route ownership model in place. The reset instead asked which facts the core repository engine truly needed to own.

The initial phase-one boundary was as important as the feature list:

  • no V1 Git database, Route/Manifest/Quarantine model, or global content-addressed product layer;
  • no upstream sync, pull/push/copy/move/promote surface, Edge entitlement, CDN bootstrap, or commercial topology;
  • no remote publication/state, object storage, GC, snapshot/freeze, Managed export, or serving in the first phase;
  • no modulemd, source-package building, Web UI, cross-Repository deduplication, or multi-writer operation.

Upstream synchronization and channel promotion were deliberately removed even though V1 had real streaming, fuzz, and deterministic-proof evidence for them. Package acquisition is an input boundary, not a second owner inside the repository engine. Operators can prepare package sets with dedicated mirror or download tooling, then let SOW own admission, membership, build, and publication. Some phase-one exclusions—publication, retention, GC, and external export—returned before the final v0.2.0 tag under the smaller Repository ownership model.

Two isolated execution modes

Plain: rebuild what the caller owns

Plain mode operates on a caller-owned directory of RPM and DEB files:

sow create /srv/repo

It scans packages, renders protocol metadata, validates the result, and installs pointers last. Plain owns its generated metadata but not a Workspace, Repository database, Generation history, or long-lived membership state.

Because the desired state is simply “the packages currently in this directory,” the correct recovery strategy is to rerun the build. Later releases made that principle even more explicit by removing Plain journals and repeated package reads.

Managed: own lifecycle and history

Managed mode introduces a Workspace with independent Repositories:

Workspace
├── sow.yml
├── .sow/                    private config, locks, SQLite, journals
└── <repository>/
    ├── pool/                canonical package bytes
    └── dists/               client protocol views

Each Repository owns its Pool, Package Objects, Membership, Desired State, Built Generation, Changeset, lock, database, and recovery state. Different Repositories do not silently deduplicate or share counters.

A Dist is a named RPM or DEB membership set. Architecture Views are projections of that membership, not new owners. Neutral packages (noarch / all) project into every applicable architecture without duplicating logical membership.

Decisions that replaced V1 concepts

V1 concept v0.2 replacement
Git commit/ref as canonical state Per-Repository SQLite plus explicit filesystem/journal evidence
Global CAS and Route materialization Repository-owned Pool and protocol views
View/Snapshot/Stable ref graph Dist membership, Desired revision, Built Generation, retained state
One universal command surface Closed Plain and Managed CLI contracts
Implicit legacy topology Strict sow.yml plus explicit names and selectors
Cross-product publication saga Repository build kept separate from target publication
Reuse V1 packages directly Narrow parser/renderer/version/signing ports only

The critical simplification was that private control state and public repository bytes could be understood without Git or an Edge router.

Desired versus Built

Managed mode made interrupted state explicit:

  • Desired State is the current package and membership intent in SQLite.
  • Built Generation is the last complete public tree that rendered and validated successfully.
  • Changeset is the exact delta between two built states.

Desired may advance while Built remains valid. A failed build therefore does not require pretending that the public tree changed or that the request never happened. Operation journals and status expose the difference.

This distinction survived every later release.

P0 through P3

The work was intentionally sequenced:

Phase Contract
P0 Plain create, real RPM/DEB metadata, signing, Pigsty-compatible filtering, pointer-last replacement
P1 Workspace/Repository/Dist lifecycle, strict config, package identity, membership, and query
P2 Add/remove/policy/build operations, journals, crash recovery, and safe mutation
P3 Check, Changeset, handoff, scale, compatibility, and clean-delivery evidence

Each phase had an API contract, implementation specification, adversarial review, and traceability/acceptance record. Those documents were useful while building the release; their lasting decisions are summarized here rather than published as separate authorities.

The first Managed RPM layout kept canonical files in a root Pool and added hardlinks below each architecture View. Metadata used a safe local pool/... href. This C2 layout made a pinned AlmaLinux 9 reposync flow work and avoided duplicate local bytes because aliases shared an inode.

The design had a hidden deployment cost:

  • object storage expands each path into a separate full object;
  • archive and cross-filesystem copies can lose hardlink identity;
  • every Dist/architecture alias looks like another canonical payload;
  • retention and GC risk depending on inode/link-count behavior;
  • the whole Repository no longer maps one-to-one to static object keys.

The pre-release design snapshot on 2026-08-05 accurately records C2 implementation and local acceptance. It is not the final v0.2.0 release contract.

The single-payload contract was written on 2026-08-05, committed as the “next” design on August 7, and implemented before the August 8 release tag. The final release therefore shipped:

<repository>/
  pool/       one canonical payload per Package Object
  dists/      metadata-only APT/RPM views

Default EL reposync became an accepted compatibility limitation; a standalone RPM leaf moved to an explicit external export. The complete reasoning is preserved in Single-Payload Repository.

What the reset reused

The team did not start from blank code. It retained only narrow components that had no V1 ownership assumptions:

  • RPM/DEB parsing and native version comparison;
  • metadata rendering, compression, signing, and validation;
  • safe filesystem primitives;
  • selected client fixtures and fault patterns;
  • exact package identity and provenance checks.

Git state, Route receipts, Edge components, legacy migration machinery, and cloud control plane code were reference material or removal candidates, not dependencies of the new core.

Release evidence boundary

The development line recorded P0/P1 acceptance on August 1, P1–P3 acceptance on August 2, and a local release-gate run on August 5. The final v0.2.0 source was tagged on August 8 after the single-payload implementation, packaging, and CI corrections landed.

The final release also shipped filesystem and R2 publication, retained generations, external RPM leaf export, and local GC. R2 remote deletion was disabled from the start and target GC retained/reporting candidates instead.

That distinction matters:

  • an August 5 C2 PASS proves the development snapshot that produced it;
  • the August 8 v0.2.0 tag and release note define what actually shipped;
  • later v0.3/v0.4 tests do not rewrite either historical result.

What still defines SOW

  • Plain and Managed are isolated modes with different recovery costs.
  • Workspace is configuration/discovery scope; Repository is package/history scope.
  • Package Object identity is immutable; Membership is logical and many-to-many.
  • Desired and Built states are distinct.
  • writes use stable locks, bounded journals, pointer-last installation, and fail-closed recovery;
  • CLI syntax, JSON envelopes, and exit classes form a closed automation contract;
  • protocol, client, mirror-tool, and provider compatibility are reported separately.

The current forms are documented in Get Started, System Model, and Design Principles.

Primary source snapshots

Continue with Design Evolution for the v0.3 and v0.4 hardening that followed.

1.8 - What SOW v0.1 Actually Proved

A curated account of 112 dated v0.1 evidence reports: the real clients, scale, failure, migration, and provider claims they supported—and what they never proved.
Version-bound evidence

Every result on this page belongs to the v0.1 source, layout, fixtures, and environment recorded at the time. It does not establish compatibility or release readiness for current SOW.

The v0.1 archive contains 112 dated evidence reports from 2026-07-11 through 2026-07-31. Publishing all of them as blog posts would make the public site less useful: many are narrow fault closures, repeated current-source audits, or transcripts for a specific implementation seam.

Discarding them would lose something equally important. The reports show which design decisions came from real clients and counterexamples, which came only from local or mock tests, and where the team deliberately refused to turn incomplete evidence into a PASS.

This ledger preserves that boundary.

Evidence timeline

Window Reports What the work concentrated on
2026-07-11 3 Real APT/DNF client baseline, 72,310-package manifest scan, 50k upstream streaming
2026-07-12 35 APT/YUM protocol behavior, CAS/GC, publication ordering, recovery, MinIO, performance, migration, build/supply-chain gates, and cloud harness design
2026-07-13 2 RPM trust closure and selected-set materialization recovery
2026-07-14–16 14 Legacy topology, family migration, physical ownership, full-copy adoption, and package-signature inventory
2026-07-17–20 39 Cloudflare/R2/COS/Edge safety, provider attestation, cutover receipts, configuration bounds, and checkpoint-fenced deletion
2026-07-22 10 Descriptor/path identity across stage, apply, prune, rollback, and residue cleanup
2026-07-26–29 7 Recursive durability, hostile-writer limits, replacement outcomes, preserved audits, and provider corrections
2026-07-30–31 2 Clean-room APT/YUM/asset MVP and final legacy-source baseline

What had strong supporting evidence

Real package clients and large repositories

The first evidence set used real APT and DNF clients rather than validating only generated XML or text. It also measured scale explicitly:

These reports justified streaming and bounded-concurrency requirements. They did not prove that every later layout inherited the same client behavior or performance.

Protocol closure and negative counterexamples

Several design changes came from negative proof rather than optimistic implementation:

The lasting lesson is not the V1 URL scheme. It is that a standards-shaped repository and a specific client transaction are different claims.

Failure and recovery

The archive exercised interrupted sync, publication, materialization, snapshot, archive, and remote-restore paths. Later reports bound file operations to descriptors and exact path identities, then covered rollback, quarantine, and preserved evidence.

Representative records include:

This work is why current SOW distinguishes pre-commit abandon, post-intent forward recovery, and contradictory evidence instead of exposing one generic “retry” path.

Legacy migration

The migration evidence enumerated old Make targets, physical layouts, repository families, consumer configuration, package-signature inventory, and rollback. It used read-only copies and exact receipts before mutation.

That program proved a particular Pigsty cutover path. It did not justify carrying legacy topology into the permanent product model. Its most valuable result was the discipline: migration assumptions become explicit data and executable gates, then retire after the cutover.

Provider and Edge experiments

The archive contains real and simulated R2, MinIO, Cloudflare, COS, and Edge work. The stronger reports state exact scope:

Some cells used owner-designated nonproduction resources; others were offline or mock validation. A real R2 result never proved COS conditional replacement, EdgeOne cache behavior, or current SOW’s target state machine.

What the archive did not prove

The evidence set was large, but its own documents retained open boundaries:

  • no one PoC established universal APT, DNF, proxy, mirror-tool, or provider compatibility;
  • MinIO S3 compatibility did not equal Cloudflare R2 or Tencent COS semantics;
  • an accepted design or adversarial review was not implementation evidence;
  • a local/mock provider result was not a production deployment;
  • a PASS remained attached to its source revision, fixture, URL layout, and environment;
  • v0.1 evidence could not validate the later Plain/Managed or single-payload designs.

Those limitations are a feature of the archive, not a defect to edit away.

What became part of the design method

The evidence program left six durable practices:

  1. Name the evidence layer. Design, source, unit/fault test, real client, provider, artifact, and production are separate.
  2. Retain negative results. A counterexample often defines a safer product boundary more clearly than another successful happy path.
  3. Bind PASS to immutable inputs. Source revision, container/client version, provider, route, and fixture belong with the result.
  4. Test scale as a contract. Large repositories need measured memory, call count, and concurrency behavior.
  5. Retire evidence with its model. Historical proof remains useful for explanation but never silently upgrades a new layout.
  6. Keep build and supply-chain checks standing. Toolchain portability, dependency vulnerability checks, static analysis, dead-code closure, and reproducible delivery are continuous release gates rather than one-time cleanup tasks.

These practices now appear in Design Principles and the current compatibility reference.

Primary source

The immutable v0.1.0 evidence directory contains every dated report. This article is the maintained interpretation; the raw files remain the audit trail.

Continue with The v0.2 Reset to see how those lessons were kept while the product model became much smaller.

1.9 - SOW v0.1 Decision Ledger: What Survived and What Was Retired

A curated ledger of all 44 surviving v0.1 ADR files, the problem each addressed, and whether its decision survived, evolved, became migration-only, or retired with V1.
Historical decision ledger

The v0.1.0 tag contains 44 ADR files numbered 0001 through 0045; 0006 is absent. This page preserves their meaning without republishing each historical implementation contract as current guidance.

An ADR is valuable even when its implementation is retired. It records the failure mode the team refused to ignore, the boundary selected at the time, and often the evidence required before a dangerous operation could proceed.

The table below classifies each decision into four fates:

Fate Meaning
Retained The principle remains part of current SOW with substantially the same boundary.
Evolved The problem and safety rule survived, but ownership or implementation changed.
Migration history The decision governed a one-time Pigsty/V1 cutover and is no longer a product contract.
Retired The decision belonged to the Git/CAS/Route/Edge product model removed from current SOW.

Primary decision dates run from 2026-07-11 through 2026-07-28; ADR-0035 carries later amendments on July 29 and July 31. Most early ADRs were consolidated into Git on July 19 after already being used by implementation and evidence work. The v0.1.0 tag tree was checked to contain the same 44 ADR paths represented below. One raw link is intentionally omitted because the historical record names obsolete infrastructure details.

Core state, publication, and trust

ADR Original decision Later fate
0001 Git was canonical state, SHA-256 CAS owned package bytes, refs expressed views/history, and publication used target sagas. Evolved. Git/CAS/Route ownership retired; single ownership, pointer-last publication, independent target state, and evidence gates survived.
0002 Public routes, immutable generations, and client/provider compatibility had separate acceptance gates. Evolved. Route machinery retired; protocol/client/provider evidence remains separate.
0003 Snapshots required verified generation copies and inventory-bound retention. Evolved. CopyObject snapshot trees retired; retained generations now store metadata and reference sets without payload copies.
0004 Edge token verification and deployment had one versioned cross-provider contract. Retired. Edge entitlement is no longer part of the SOW repository engine.
0005 Every legacy Make target was mapped to SOW, retirement, or policy rejection. Migration history. The mapping completed its cutover role and left the active product.
0007 Public and gated packages shared one canonical repository instead of a separate Pro bucket. Retired. The commercial Edge topology left product scope; the more general single-owner lesson remains.
0008 GC followed complete reachability and required explicit confirmation for destructive work. Retained. Current local and target GC still require closure, exact identity, grace, and capability.
0009 Once a remote generation became visible, recovery rolled forward rather than inventing rollback. Retained. Commit intent remains the boundary between abandon/reconcile and forward-only recovery.
0010 Metadata signing bound exact public/private identities and treated rotation as a repository-wide transition. Evolved. Current signer evidence and trust-ring rules preserve exact identity without the V1 ref topology.
0011 Remote deletion required target ownership, inventory, checkpoint, grace, and conditional-delete evidence. Retained. R2 remains report-only because it cannot satisfy the required atomic delete capability.
0012 Private origins, cache topology, and exact purge mapping were explicit deployment contracts. Retired. CDN/private-origin deployment is outside current SOW.
0013 A persisted plan was recovery input to revalidate, never authority to replay blindly. Retained. Managed recovery still rebinds plans, files, target identity, and public evidence.
0014 RPM provenance recorded exact packet evidence and distinguished ingestion policy from historical proof. Evolved. Current package authentication and independent trust rings replace the V1 receipt encoding.
0015 Configuration named a target; published refs and checkpoints belonged to canonical state. Retained. Target-neutral generations and target-prefix attempts/checkpoints remain separate owners.
0016 Cloud adapters used concrete, bounded SDK/API contracts instead of a generic provider abstraction. Evolved. Concrete provider boundaries survived; V1 Edge/COS details retired.
0017 Accepted RPM signatures had to close against a stable repository keyring and opened payload identity. Evolved. v0.4 independent multi-ring verification strengthens the same trust boundary.
0018 A selected package set staged, validated, committed, and recovered as one bounded local transaction. Evolved. V1 materialization retired; current Plain/Managed pointer-last staging keeps the transactional lesson.

Legacy topology, compatibility, and migration

ADR Original decision Later fate
0019 EL7 metadata format and compressor behavior were frozen for legacy consumers. Migration history. Current compatibility documentation, not this freeze, defines supported platforms.
0020 Every legacy physical path and selector group received an explicit owner. Migration history. It prevented ambiguous cutover but is not a current repository model.
0021 Cross-EL yum/infra/{arch} projections were reproduced with exact frozen evidence. Migration history. The special projection left active product scope.
0022 Package history could not be broken merely because physical ownership moved. Evolved. Generations, retained references, and migration journals now preserve continuity without V1 routes.
0023 Canonical Git objects were rebound to exact bytes before admission and use. Evolved. Git authority retired; descriptor/path/digest identity checks remain.
0024 Materialized route receipts acted as narrow read/retirement capabilities. Evolved. Route receipts retired; capability-bound inspection and deletion survived.
0025 Locks bound an exact process instance and stable lock inode rather than elapsed time. Evolved. Stable lock inodes and rejection of timeout-based lock stealing survived; the V1 process-instance lease mechanism did not.
0026 Offline archive creation used a durable intent and strict archive admission. Retired. Offline archive projection left current SOW scope.
0027 Legacy bytes entered canonical state only after exact path, package, and provenance admission. Migration history. The adoption program completed; strict package admission remains in narrower form.
0028 DEB inspection opened only the control archive needed for metadata and rejected ambiguous containers. Retained. Narrow parsing and bounded input remain part of the APT package boundary.
0029 Client floors and the EL8 freeze were explicit owner policy, not inferred from code. Migration history. Current platform policy lives in the compatibility reference and release notes.
0030 Missing legacy YUM bodies could be repaired only from a reviewed blocker-set digest. Migration history. The narrow negative-provenance exception did not become a general ignore flag.
0031 Gated legacy content required exact checksum repair and activation evidence. Retired. Pro activation left product scope; fail-closed repair remained a general lesson.

Provider and Edge control plane

ADR Original decision Later fate
0032 Only one exact owner-designated Cloudflare test tuple could bypass normal production rejection. Retired. It was a narrow historical test exception, never a general deployment rule.
0033 Read-only provider readiness had its own registry and ownership evidence. Retired. Provider bootstrap registry left current SOW.
0034 Worker bootstrap used leases, two-phase recovery, exact resources, and reversible state. Retired. Cloudflare deployment is no longer repository-engine responsibility.
0035 Provider identity, runtime bindings, log sink, and lease ownership were attested before use. Retired. The exact Edge control plane left product scope.
0036 R2 lacked conditional DeleteObject, so an explicit checkpoint-fenced unconditional-delete fallback was allowed behind a deterministic capability probe and repeated identity/fence proofs. Evolved; fallback not inherited. Capability probing and fail-closed defaults survived, but current SOW disables R2 remote deletion and keeps target GC report-only.
0037 Gated publication had to prove denial at the Edge before uploading confidential bytes. Evolved. The Edge product surface retired; proving authorization before exposure survived.
0038 YUM cutover required an expiring receipt bound to exact endpoints, generation, and trust bytes. Migration history. It made a dangerous consumer cutover auditable and then retired.
0039 Caret had one canonical URL spelling across Go, Edge, logs, and origin routing. Evolved. The route was retired; canonical encode-once path handling remains.

Bounded configuration and derived-state recovery

ADR Original decision Later fate
0040 Configuration cardinality and expanded topology were bounded before allocation or execution. Retained. Current parsers and state wires keep explicit size/cardinality limits.
0041 Unknown final projection stages were preserved for audit rather than guessed away. Evolved. Current recovery still preserves contradictory evidence and fails closed.
0042 Derived-state replacement had explicit success, rollback, preserved, and blocked outcomes. Retained. Current operations report precise recovery outcomes instead of collapsing them into success/failure.
0043 File mutation bound descriptor identity and stated the same-UID hostile-writer limit honestly. Retained. Path safety still fails closed without claiming protection outside its OS ownership boundary.
0044 Preserved audit copies could be retired only through an exact capability and confirmation token. Evolved. The V1 command retired; exact capability-bound deletion remains a design rule.
0045 Unjournaled residue had a bounded classifier and could never be silently adopted or deleted. Retained. Current recovery distinguishes known state, safe residue, and contradictory evidence.

The pattern behind the ledger

The decisions that survived were not the most elaborate V1 mechanisms. They were the small invariants beneath them:

  • one owner for each fact;
  • exact identity before mutation;
  • immutable preparation before pointer commit;
  • forward recovery after commit intent;
  • complete evidence before deletion;
  • explicit bounds and honest non-goals;
  • compatibility claims attached to the client/provider actually tested.

The Git database, route graph, Edge bootstrap, and migration commands were replaceable. These invariants were not. They are maintained today in Design Principles, System Model, and Publication & Recovery.

The v0.1.0 ADR directory remains the immutable primary source. The next article summarizes the separate v0.1 evidence record.

1.10 - The Original SOW v0.1 Program: A Git for Package Repositories

Why SOW began as a Git/CAS/Route/Edge control plane, what that model achieved, and why the product later reset around a smaller Repository core.
Historical design record

This article describes the v0.1 program initiated on 2026-07-11 and sealed in the v0.1.0 source baseline on 2026-07-31. The Git/CAS/Route/Edge product model is retired. Current behavior is defined by SOW Docs and the maintained System Model.

SOW did not begin as a small repository indexer. The original goal was to replace a large Pigsty Makefile-and-rclone workflow with one Go control plane for APT, YUM, and static assets. It would own local state, package lifecycle, upstream synchronization, channels, views, snapshots, publication to two independent clouds, CDN behavior, commercial access control, migration, recovery, and verification.

The phrase used at the time was “Git for artifact repositories.” It was a coherent answer to a real problem: package distribution has immutable content, mutable names, history, promotion, publication, and rollback. The difficulty was not that the analogy was wrong. The difficulty was how much product surface the analogy invited SOW to own.

The problem the first design tried to solve

The legacy system already published dozens of stable URLs used by APT, DNF, scripts, and installation endpoints. A replacement had to preserve those URLs while adding properties the Makefile workflow could not express safely:

  • one identity for every accepted package body;
  • desired state separated from what each cloud had actually published;
  • snapshots and retained history without guessing which files were still live;
  • incremental upload and purge proportional to the change set;
  • recovery after a crash between metadata preparation and pointer publication;
  • provenance for upstream indexes, package signatures, and migration exceptions;
  • private commercial objects that could not leak through a public index or cache key;
  • compatibility evidence from real APT/DNF clients rather than metadata syntax alone.

The original program deliberately treated these as one connected ownership problem.

The selected v0.1 model

Git was canonical state

.sow/state was a normal Git worktree operated through an embedded Go library. Manifests, refs, provenance, configuration hashes, target checkpoints, and security labels were Git content. SQLite was only a rebuildable query projection.

This gave every state transition a durable history and made one ref update the local commit boundary. It also meant that package-management state, publication state, migration state, and operator intent all had to fit one Git-shaped authority.

CAS owned the bytes

Package bodies lived in an immutable SHA-256 pool. Published trees used hardlinks into that pool, so reachability rather than filenames decided whether a byte could be collected. This made local deduplication and history inexpensive, but required one filesystem and made materialized paths, receipts, and inode identity part of the product contract.

Refs expressed product meaning

Repository, View, Snapshot, Stable, History, and per-target remote refs described the meaning and retention of content. A mutable view could move without erasing stable or historical reachability. Removing a view changed references; garbage collection remained a separate, evidence-gated operation.

Publication was a saga

Cloudflare R2 and Tencent COS were independent targets. Each target prepared immutable objects, persisted commit intent, moved protocol pointers, purged the CDN, verified public visibility, and advanced its own checkpoint. One target succeeding could not be rolled back merely because the other failed.

The important order was already recognizable:

immutable payload
  -> immutable metadata
  -> durable commit intent
  -> mutable protocol pointer
  -> public verification
  -> checkpoint
  -> grace
  -> evidence-gated deletion

That sequence survives in current SOW.

Routes and Edge were part of repository correctness

The public tree preserved legacy paths, while generation-aware routes selected immutable metadata. Private objects lived behind an Edge contract shared by Cloudflare Worker and EdgeOne. Authentication had to run before origin access; tokens could not enter origin URLs, cache keys, or logs.

This closed the confidentiality problem, but also made CDN routing, token verification, provider deployment, log sinks, and cache topology prerequisites of the repository model.

Why the design was attractive

The v0.1 model had several strong properties:

  • every durable fact had an explicit owner;
  • immutable bytes and mutable names were separated;
  • local and remote publication states were observable independently;
  • pointer-last publication made partial work recoverable;
  • GC required a complete reachability closure;
  • plans and receipts were revalidated instead of blindly trusted;
  • client compatibility, provider compatibility, and implementation evidence were distinct;
  • the exact legacy migration surface was treated as data, not tribal knowledge.

The later product did not discard these ideas. It kept them after removing much of the machinery that first expressed them.

Why the product reset

By the end of July, SOW v0.1 had accumulated:

  • Git commits and refs as an internal database protocol;
  • a global content-addressed store and hardlink materialization layer;
  • Route, View, Snapshot, Generation, Projection, Receipt, and Lease concepts;
  • APT/YUM/asset synchronization and legacy migration inside the core binary;
  • cloud SDK, edge bootstrap, provider attestation, purge, token, and private-origin logic;
  • forty-four surviving ADR files and 112 dated evidence reports.

Each individual addition answered a real failure mode. Together they created several problems.

First, too many objects could appear to own the same package and public path. A package was simultaneously a CAS object, a manifest entry, a View member, a materialized Route, a Snapshot reference, and a remote target object.

Second, local repository generation was coupled to cloud and Edge lifecycle decisions. An operator who only wanted to build a YUM or APT repository inherited the conceptual cost of commercial access control and distributed publication.

Third, the migration program had become a permanent product subsystem. Exact legacy topology, Make targets, CDN behavior, and old provider exceptions were valuable during cutover but did not belong in the long-term repository abstraction.

Finally, recovering every derived path under hostile-writer and crash conditions required more identity, journal, quarantine, and retirement machinery than the core job justified.

Upstream synchronization and channel promotion were another deliberate scope cut. V1 had real streaming, fuzz, URL, and proof-order evidence for fetching packages, but acquisition introduced its own policy, retry, provenance, and remote-ownership model. The reset made package acquisition an external input concern so SOW could own repository admission and publication without also becoming a universal mirror orchestrator.

What survived the reset

v0.1 lesson Maintained form
Every durable fact has one owner Repository and target-prefix ownership in System Model
Canonical data differs from projections One pool/ plus metadata-only dists/
Pointers commit after payload preparation Publication & Recovery
Post-commit recovery is forward-only Managed operation and publication journals
Plans and paths are untrusted input Exact identity, containment, and rebind checks
Deletion requires complete evidence Reference closure, grace, absence, and provider capability
Generated server config is derived state The complete Repository root is the hosting unit; serving remains explicit operator configuration
Compatibility is a matrix Platforms & Integrations
Historical evidence never upgrades itself Dated Design and Release records

What was retired

  • Git as product state and SQLite as a disposable cache;
  • a cross-product CAS and hardlink-based materialization layer;
  • the Route/View/Snapshot public command model;
  • generated Nginx includes, Route receipts, and V1 serving-control state;
  • built-in upstream synchronization, promotion, and legacy Make-target migration;
  • Edge token entitlement and private-origin topology as repository prerequisites;
  • the multi-cloud publication control plane and provider bootstrap/attestation system;
  • V1-specific projection receipts, leases, quarantine commands, and recovery surfaces.

Some implementation ideas later returned in smaller forms, but the ownership boundary changed: current SOW is a repository engine first, not a distribution platform for every surrounding concern.

Timeline

Date Milestone
2026-07-11 Goal, brainstorm, technical research, client and scale baselines
2026-07-12 Core Git/CAS/publication contract and first protocol/provider evidence
2026-07-13–16 Package trust, transaction, legacy topology, and migration closure
2026-07-17–20 Provider bootstrap, Edge confidentiality, deletion capability, and input bounds
2026-07-22–29 Path-identity, hostile-writer, quarantine, and residue-recovery hardening
2026-07-30–31 Clean-room MVP evidence and v0.1.0 source seal
2026-08-01 onward Plain/Managed reset and eventual retirement of the V1 runtime

Primary sources and next records

The immutable v0.1.0 docs/ tree preserves the original architecture contract, requirements traceability, migration material, ADRs, and evidence. Those files explain the historical implementation; they do not override this site’s maintained history or current documentation.

Continue with the v0.1 Decision Ledger, v0.1 Evidence Ledger, and v0.2 Reset.

2 - Release Notes

SOW release notes covering features, performance, correctness, packaging, and verification.

SOW release notes cover features, performance, correctness, packaging, and verification, with the newest version first.

v0.5.0 adds publication timestamps for existing Plain YUM repositories. For the earlier Managed verification and migration changes, see v0.4.0.

2.1 - SOW v0.5.0

SOW v0.5.0 adds explicit publication timestamps for Plain RPM repositories, preserves deterministic builds, and documents migration from existing YUM maintenance workflows.

SOW 0.5.0 focuses on adopting SOW for existing flat YUM repositories. The main addition is sow create --metadata-timestamp SECONDS: publishers can retain a valid publication time when replacing another metadata generator, including repositories used by EL7 YUM clients.

See Download SOW for installation options and the upgrade guide for an existing workspace.

Publication time for an existing YUM repository

EL7 YUM compares the largest metadata timestamp in a downloaded repomd.xml with its cached copy. If the new index has an older timestamp, YUM can reject it and retain the cached index. A new checksum, a different revision, or a newer file modification time does not satisfy this comparison.

Plain create previously always wrote zero data timestamps. That is reproducible, but it is unsuitable as a drop-in replacement for a published index with positive timestamps. Version 0.5 adds an explicit way to choose the publication time:

# PUBLISH_TIME is chosen by the publisher from its recorded publication history.
sow create /srv/yum.candidate --metadata-timestamp "$PUBLISH_TIME"

SECONDS is an integer from 0 to 253402300799. Negative, malformed, overflowing, or duplicate values are usage errors. The option belongs to create; it is not a Managed build, publish, or RPM leaf-export option.

Property Behavior in 0.5
Default data timestamp Still 0
Explicit timestamp Written to the three RPM repomd.xml data records
repomd.xml revision Still 0 in Plain mode
RPM bytes and package timestamps Unchanged by this option
Compressed XML and gzip headers Unchanged when only this option changes
DEB indexes Unchanged by this option
Repeated metadata build Same package bytes and options produce the same metadata and a no-op; forced re-signing is a separate package mutation

SOW does not read the wall clock or maintain a publication clock on the caller’s behalf. A maintenance wrapper should retain an external high-water mark, preserve a valid index and signature for unchanged content, and advance the time for real updates. Restoring old package content is a new publication and must not restore an older publication time. If an earlier conversion has already reset the live timestamp to zero, include trusted pre-conversion backups when reconstructing that high-water mark.

Changing repomd.xml invalidates its old detached signature. The publisher must sign and verify the new index before exposing it. --sign-with signs RPM packages; it does not sign repomd.xml. The migration guide covers the complete sequence and the boundary between the SOW command and the surrounding maintenance flow.

RPM dependency compatibility

The dependency projection keeps the modern createrepo_c treatment of pre-transaction and post-transaction script requirements. New regression cases cover those phases alone and in the combinations seen in Cloudberry, Percona, and Grafana packages.

This is additional coverage, not a change to dependency parsing or a rollback to createrepo_c 0.20.1 output. A difference in the number of dependency rows or pre="1" attributes is not by itself evidence of missing dependencies. The relevant upstream change is createrepo_c PR #427.

Repository and publication fixes

This release prevents historical Dist-removal cleanup from deleting a package that has been re-added to another Dist. Signing-policy and pool-path checks now reject incompatible inputs before committing Desired changes, and interrupted operations from earlier binaries have bounded recovery paths that preserve committed data and still reject altered bytes.

Published payload paths cannot be reused for different content, even after local GC. Publication recovery never overwrites an immutable payload. Reader-held SQLite snapshots no longer turn a durable write into a reported checkpoint failure. External input paths can traverse symlink ancestors while package leaves retain their existing checks.

APT metadata verification accepts GnuPG’s final-newline convention without ignoring other content differences. Newly generated GPG metadata signatures use SHA-256 regardless of local GPG preferences, and new metadata builds require a currently usable signing key. Archives and native packages now include third-party license notices.

Pool paths are compared case-insensitively even for identical bytes, so a file name that differs only by case cannot become a second URL. On case-insensitive filesystems SOW also rejects source directories that differ only by case, in the workspace and before any upload to a filesystem target. Concurrent export rpm-leaf --hardlink runs and check no longer report unchanged Pool bytes as an integrity failure. Local gc ignores publication inventory entries that exist only remotely, such as payloads retained by report-only R2 maintenance.

sow repo migrate reports the schema change, and an unmigrated Repository names the command to run. Exit 130 is reserved for the command’s own interruption. file:// R2 credentials must be regular files, and a v0.4.0 add that omitted a non-empty, fully excluded Dist now recovers on the next build.

Validate the surrounding maintenance workflow

A migration should exercise each deployed YUM/DNF version and architecture with an existing metadata cache. Keep the same URL and repository ID, move from the old index to the SOW index and then to another content generation, and verify index refresh, actual package download, and the configured package and repository signature checks. Retain the commands, source revision, input manifests, and results with any claimed package or transition counts.

The wrapper’s persistent clock, signature reuse, and selective re-signing are not new built-in sow create features. Plain mode remains a flat, multi-file rebuild: it does not generate legacy SQLite metadata or module streams, and it does not provide an atomic whole-repository switch.

Upgrade and developer notes

  • Upgrading a 0.3 or 0.4 Managed workspace requires a backup and explicit sow repo migrate. Schema v13 indexes candidate pool paths; sow/v3 configuration and the public pool/ + dists/ layout are unchanged. Then build and check each Repository: affected RPM authentication and APT metadata contracts refresh once, and subsequent unchanged RPMs reuse matching Built evidence.
  • Cancellation records a terminal failure before cleaning uncommitted package bytes; commands interrupted with Ctrl-C return 130. Empty, fully excluded additions recover without stranding the Repository. Long readers can defer post-prune space reclamation.
  • Agent metadata signing freezes time and pins the actual usable subkey after one small probe per identity/time. New APT publication rejects weak signature digests while historical verification and frozen recovery keep their original meaning.
  • Maintained documentation is consolidated on this site. Compatibility and performance checks now live in qa/compat/ and qa/perf/; shared RPM fixtures live in internal/testdata/. The clean-delivery manifests follow those paths.
  • Source builds now require Go 1.27.1. The dependency graph uses x/crypto v0.56.0, and release workflows pin GoReleaser v2.18.2 and its action commit.
  • The S3-compatible integration test runs against a digest-pinned pgsty/silo image, the MinIO-compatible object store maintained by PGSTY, because the upstream minio/minio image is no longer available on Docker Hub.
  • RPM format v6 signatures are not supported in 0.5.0; use RPM format v4 packages for package-signing workflows.
  • The 2026-09-29 source govulncheck found no reachable vulnerabilities. A required module still carries advisory GO-2026-5932, but the current code does not import its affected package; scans of stripped binaries can still report the module match. This does not claim that all required modules are advisory-free; scan the final release commit again.
  • Before tagging, require the full Go suite, race checks, static analysis, vulnerability and upstream-provenance checks, deterministic source delivery, client integration, and archive/package verification for the final release revision. Local validation does not replace successful CI and Integration runs for that exact commit.

For a new flat directory, the existing sow create DIR workflow remains valid. For a published YUM repository, start with Migrating an Existing YUM Repository and verify that the binary’s create --help lists --metadata-timestamp before switching the maintenance command.

2.2 - SOW v0.4.0

SOW v0.4.0 makes Managed verification single-pass, adds independently verified RPM trust rings and safe publication-target rebinding, and hardens migration, recovery, and public delivery checks.

SOW 0.4.0 is an integrity and recovery release for Managed repositories. It makes the deep checker’s I/O contract explicit, prevents RPM trust from being assembled across unrelated keys, adds an audited way to correct mutable publication-target settings, and closes the remaining v0.3 migration and interrupted-publication gaps.

Plain repository behavior and the public pool/ + dists/ layout do not change.

Upgrade from 0.3

Migrate every v0.3 Repository explicitly

Stop all Workspace writers, back up the Workspace, install 0.4.0, and run sow repo migrate REPOSITORY once for each Repository before ordinary reads or writes. The migration is explicit and one-way; do not reopen a migrated database with SOW 0.3.

cp -a /srv/sow /srv/sow.backup-before-0.4.0
sow repo migrate pigsty -C /srv/sow
sow repo migrate pgsql -C /srv/sow
sow check -r pigsty -C /srv/sow
sow check -r pgsql -C /srv/sow

Schema v11 repairs the v0.3 Dist lifecycle case that could leave a Repository marked clean while another Dist remained dirty. Repository status is now derived in the same transaction that changes its Dists. The migration also repairs publication and signer evidence without inventing a historical signing identity: an identity that v0.3 never recorded remains explicitly unverified and cannot become a retained trust assertion.

Schema v12 backfills an append-only publication-target binding ledger. Initial binds, migration backfills, and later operator-confirmed rebinds are separate immutable revisions.

One authenticity pass per physical payload

Every sow check now performs exactly one authoritative content hash for each unique physical package payload, even when its cached fingerprint still matches. Evidence is bound to device, inode, size, mtime, and ctime, and to the descriptor that was actually read. Hard links to the same physical object can share the proof; a replacement or concurrent identity change invalidates it and fails closed.

The same authenticated evidence is reused while validating retained Generations, walking the final manifest, and producing sow changes. Retaining several Generations or adding more Dists therefore does not multiply payload hashing. DEBs and unsigned RPMs require one full payload stream; a signed RPM uses at most one additional main-header-to-EOF signature stream, independent of the number of Dists or trust rings.

Package-facts reads are likewise scoped to the selected digest set in bounded, deterministic SQLite batches. A warm 64 MiB build performs no package-body read. Fingerprint drift and a missing facts row share one authoritative payload pass instead of triggering separate scans.

Independent RPM trust rings

Embedded RPM signatures are verified against each candidate trust ring independently. Every recognized signature packet must verify inside one ring, and at least one verified path must authenticate the payload. This preserves support for historical CentOS v3/v4 signatures and deliberately dual-signed packages while preventing a retained single-key claim from being assembled out of packet-by-key successes from different rings.

The current policy ring, each retained single-key ring, and the combined trusted ring all consume the same signature stream. Adding keys changes authorization decisions, not the number of package reads.

Safe target rebind and public verification

sow publish TARGET --rebind is the explicit operator-confirmed path for correcting a target’s mutable configuration while preserving its storage identity and recovery state.

May change with --rebind Requires a new target
target name Repository identity
public_endpoint provider or storage endpoint
max_cache_ttl region, bucket, or prefix

An ordinary publish reports the mismatch and points to --rebind; it never silently adopts the new values. Rebind uses the same exclusive locks as publish, appends an audit revision, and can resume an active commit-intent attempt forward. TTL changes are refused while target maintenance is pending, and a filesystem public endpoint cannot change during conditional-delete maintenance. Historical grace deadlines are never shortened.

Filesystem and R2 HTTP(S) public endpoints now share one hardened verifier. Response headers and body idle progress have separate deadlines, so a large body may stream for longer than two minutes as long as it continues making progress. Ordinary canonical GETs remain authoritative; no-cache requests only prompt revalidation. Stale content and 404s honor max_cache_ttl, 408/425/429/5xx responses get a short bounded retry window, and an oversized response fails closed. Filesystem absence requires canonical 404/410 visibility. R2 remote deletion remains deliberately disabled and report-only.

Prospective filesystem target paths are also checked for aliases before a durable bind, including case aliases on case-insensitive volumes. A failed preflight leaves no binding row and creates no target prefix.

Recovery, CLI, and implementation cleanup

  • Incremental publication recovery accepts only the exact old checkpoint or target Generation bytes at each pointer. Unknown or third identities still fail closed.
  • Routine publication verifies the exact changed object set rather than downloading the full public Generation. No-op and applied-checkpoint recovery reuse complete private inventory evidence, while target GC scans only the protocol pointers it needs.
  • R2 operations use phase-specific header/idle deadlines, bounded conditional multipart upload, retryable reads and writes, and a whole-object SHA-256 check before multipart completion.
  • Generation signer rows are now an exact manifest side table across partial builds, Dist changes, migration, retention, and local GC.
  • Repeating a default sow add converges a selected Dist left dirty by an earlier --skip or configuration change, even when the package object itself is reused.
  • sow rm --check shares the mutating configuration guard and returns an exact no-write preview; its result no longer depends on the scratch filesystem device.
  • Every Managed command now has a human-readable renderer. --json retains the stable sow.cli/v1 envelope, including committed partial results and diagnostic check/preview results on failure.
  • The retired APT v1 builder, external sort, empty-Dist implementation, and Git-tracked by-hash ledger have been removed. Current Plain and Managed APT parsing/rendering remain covered.

Verification and artifacts

The release gates cover the full Go test suite, vet, staticcheck, dead-code reachability, vulnerability and RPM-fork provenance checks, race tests, Linux amd64/arm64 builds, and the deterministic payload/facts I/O contracts above. The release binaries were built with Go 1.27.0 from source tag v0.4.0. Building from source now requires Go 1.27.0 or newer, up from 1.26.5 in 0.3. The AWS SDK, SQLite, compression, and cryptography dependencies were refreshed alongside it, and the quality gates pin staticcheck v0.8.1, deadcode v0.49.0, and govulncheck v1.7.0.

The release set contains four Linux/macOS archives, two RPMs, two DEBs, and SHA256SUMS. Every archive contains sow, README.md, CHANGELOG.md, and LICENSE; native Linux packages install the Apache-2.0 license with the binary.

Get the release

Use the download page for platform-specific commands and verified digests, or inspect the GitHub release. After installation, run sow version, migrate each v0.3 Repository, and finish with sow check before publication.

The maintained contracts are documented in Managed Workspaces, Signing Model, and Publication & Recovery.

2.3 - SOW v0.3.0

SOW v0.3.0 reduces package work across Plain and Managed repositories, adds cached package facts and bounded commits, tightens durability, and consolidates release quality gates.

SOW 0.3.0 is a performance, durability, and product-focus release. Plain repository generation completes with one package-content pass. Managed repositories avoid per-object membership queries, reuse authenticated package facts, and promote payloads in bounded group commits. The shipping binary and its release pipeline are also consolidated around the repository workflows that SOW supports.

Plain: one package-content pass

The default unsigned sow create path hashes and parses each RPM or DEB once, with parallelism controlled by --jobs. Metadata is rendered from the retained inspection result. Before publication, SOW performs one final package-set and file-stat snapshot check, catching concurrent input changes without reading and hashing every package again.

Plain metadata is rebuildable derived state. The implementation does not create an operation journal, recovery trash, rollback pre-images, or repeated package hashes. If a run is interrupted, rerun sow create and SOW reconstructs the metadata from the package directory.

Pigsty preprocessing retains RPMs whose headers report i386, i486, i586, or i686, so those packages remain in repository metadata and the repo_complete checksum manifest. The intended DEB i386 and exact Patroni 3.0.4 filters still apply.

Managed: scale without relaxing integrity

Membership tables have reverse indexes by package SHA-256, and Desired and Built Membership are expanded with one ordered bulk projection instead of one query per object. In the project benchmark, listing a 5,000-object Dist fell from about 4.1 seconds to 33 milliseconds. A 50,000-object Dist completes in roughly 300 milliseconds instead of exceeding ten minutes.

A rebuildable package-facts cache is keyed by immutable package SHA-256. Ingest authenticates and parses each new RPM or DEB once and retains the view-independent facts needed to render metadata. Builds bulk-load those facts, match them in memory, and lazily rebuild missing or corrupt rows from authenticated package bytes.

On an unchanged Pool, warm builds validate payloads with device, inode, size, mtime, and ctime fingerprints and avoid package-body reads. Fingerprint drift triggers one authoritative SHA-256 pass and repairs the cache path. sow check remains the explicit full cryptographic audit. RPM metadata artifacts and DEB architecture indexes use bounded --jobs concurrency, Generation manifest and changeset rows are inserted in batches, and final normalization reuses its descriptor snapshot instead of scanning the Pool again.

Bounded commits and observable builds

Managed payload promotion is a bounded, single-writer group commit. Each batch handles at most 512 objects or 1 GiB: SOW creates the public Pool links, persists the distinct target directories, removes the pending names, and then persists the shared pending directory. A crash therefore leaves a recoverable pending-only, exact dual-link, or Pool-only state; durable loss of both names remains an integrity failure.

Pending payloads use their final 0644 mode inside the private 0700 pending directory, making promotion a namespace operation. The pending-source guard records object identity without holding one descriptor per package for the whole build, keeping descriptor use bounded. Publication also persists each target directory entry before unlinking its source name.

Long builds append structured build_progress events for rendering, payload promotion, Dist publication, normalization, and finalization. These events are visible in sow log and do not add a database checkpoint to each phase, so progress reporting cannot turn a successful build into a failed one.

Focused publication and runtime

The R2 publication transport is limited to the storage primitives SOW uses: list, head, get, and conditional put. Remote garbage collection remains report-only and does not delete objects. Unused cloud-control, CDN, Edge-worker, migration-program, and alternate runtime paths have been removed from the active tree, leaving one repository core behind the production CLI. The default go test ./... run therefore covers the complete active implementation.

Correctness and release quality

  • Local GC safely removes a unique case-folded Pool alias recorded by a Generation, which matters on case-insensitive filesystems. Path, size, and digest must still identify the same immutable object; ambiguous or drifted aliases fail closed.
  • Every archive includes LICENSE. RPM and DEB packages declare Apache-2.0 and install the license at /usr/share/licenses/sow/LICENSE and /usr/share/doc/sow/copyright.
  • CI enforces formatting, module tidiness, vet, static analysis, dead-code checks, performance-test compilation, the full test suite, race tests, clean-delivery checks, and package snapshots.
  • Integration gates exercise the production binary in a clean-room mixed RPM/DEB workflow, an exact unsigned Plain APT install on Ubuntu 22.04, DNF signature-transition probes on AlmaLinux 8/9/10, and conditional S3 operations against pinned MinIO.
  • The published release contains macOS and Linux archives for amd64 and arm64, RPM and DEB packages for both Linux architectures, and SHA256SUMS.

Get the release

Use the download page for platform-specific commands, or inspect every asset on the GitHub release. After installation, run sow version to verify the selected binary.

The operating contracts are documented in Plain Mode, Managed Mode, and Platforms & Integrations.

2.4 - SOW v0.2.0

SOW v0.2.0 provides Plain and Managed RPM/DEB repositories, verified generations, signing, publication, retention, GC, and RPM leaf export.

SOW 0.2.0 is a self-contained RPM and DEB repository manager from Pigsty. Release artifacts target Linux and macOS as single Go executables.

Two operating modes

Plain mode indexes RPM and DEB files already present at one directory’s top level:

sow create /srv/repo

It writes repodata/, Packages, and Packages.gz in place. Plain mode has no Workspace, state database, generations, or DEB Release signing.

Managed mode owns package membership and lifecycle:

mkdir -p /srv/sow && cd /srv/sow
sow init .
sow repo new pigsty
sow dist new el9 --format rpm -r pigsty
sow add /path/to/packages/*.rpm -r pigsty -d el9
sow check -r pigsty

Each accepted package body is stored once beneath pool/. RPM and APT client views live beneath dists/ and are materialized as immutable Generations.

Lifecycle controls

Managed repositories include:

  • strict sow/v3 configuration and explicit membership policy;
  • RPM metadata, APT metadata, and optional RPM package signing;
  • cheap status plus nine-layer check for publication gating;
  • recoverable filesystem and R2 publication attempts;
  • explicit retained Generations and reachability-based local GC;
  • standalone rpm-leaf export for consumers that reject parent-relative rpm-md paths.

The canonical Managed tree must be delivered as a complete repository. Use sow publish for configured targets or stage a whole-root copy offline before an atomic switch. Do not update a live repository file by file.

Compatibility evidence

The active test suite proves clean-room current-CLI builds for both formats, a Plain APT consumer on Ubuntu 22.04, RPM detached-signature behavior in AlmaLinux 8/9/10 probes, and an isolated S3-compatible provider fixture. Those probes do not by themselves establish a complete current Managed DNF/APT or R2 CLI acceptance gate.

See Compatibility for the exact claim boundary and Quick Start for a fresh installation path.