This is the multi-page printable view of this section. .
Release Notes
- 1: SOW v0.5.0
- 2: SOW v0.4.0
- 3: SOW v0.3.0
- 4: SOW v0.2.0
1 - SOW v0.5.0
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:
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/v3configuration and the publicpool/ + 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/andqa/perf/; shared RPM fixtures live ininternal/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/siloimage, the MinIO-compatible object store maintained by PGSTY, because the upstreamminio/minioimage 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
govulncheckfound no reachable vulnerabilities. A required module still carries advisoryGO-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 - SOW v0.4.0
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
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.
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 addconverges a selected Dist left dirty by an earlier--skipor configuration change, even when the package object itself is reused. sow rm --checkshares 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.
--jsonretains the stablesow.cli/v1envelope, 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.
3 - SOW v0.3.0
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/LICENSEand/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.
4 - SOW v0.2.0
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:
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:
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/v3configuration and explicit membership policy; - RPM metadata, APT metadata, and optional RPM package signing;
- cheap
statusplus nine-layercheckfor publication gating; - recoverable filesystem and R2 publication attempts;
- explicit retained Generations and reachability-based local GC;
- standalone
rpm-leafexport 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.