| 1 |
# Cylinder: artifacts, the Oxygen way |
| 2 |
|
| 3 |
Status: Draft 0.3 (proposal revision 3), 2026-10-12. Draft 0.2 added the |
| 4 |
object model, deployment leases, inverted vulnerability indexing, |
| 5 |
multi-party consensus, agent scopes and revset queries; 0.3 corrects the |
| 6 |
MicroVM mount path, the reach of the file index, revocation, encryption, |
| 7 |
leases and upstream quarantine, and keeps §7 open. C0 is built; the rest |
| 8 |
is not. "Cylinder" is a working name (the artifact-repository idea parked |
| 9 |
on 2026-10-02); nothing here is decided until Andy says so. |
| 10 |
|
| 11 |
**Cylinder** keeps and moves everything a build produces: runner images, |
| 12 |
container images, packages, binaries, SBOMs and reports. It would be a |
| 13 |
service of its own, embedded in OxygenHub by default and shown through |
| 14 |
the hub's UI, the same pattern as Jetstream. It is meant to be better |
| 15 |
than what exists, not compatible with it; compatibility is an edge we add |
| 16 |
for adoption, never the core. |
| 17 |
|
| 18 |
## 1. What exists today |
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
| **Docker Hub** | The default namespace; huge public catalogue; official images; scanning (Scout). | Pull limits since April 2025: 10 an hour anonymous, 100 an hour on a free account. SaaS only. Tags are mutable. Signatures and provenance are separate artifacts; referrers are still in beta. Nothing links an image to the source it came from. | |
| 23 |
| **GitHub** (GHCR, Packages) | Next to the code and Actions; free container storage for now; the best Sigstore integration; artifact attestations. | Storage billing for private images deferred, not settled. One silo per package format. Auth by personal access tokens. Tags are mutable: the tj-actions attack (March 2025, CVE-2025-30066) moved every tag of a popular action to a malicious commit at once. No self-hosting outside Enterprise Server. | |
| 24 |
| **GitLab** | A registry per project; many package formats; a dependency proxy; self-hostable, so it can be sovereign. | Cleanup policies delete tags only; layers stay until an administrator runs garbage collection. The registry is a separate service to run and scale. Signing means bringing cosign yourself. | |
| 25 |
| **Bitbucket** (Packages) | GA since December 2025; permissions inherited from the repository; garbage collection runs by itself; Maven and npm since April 2026. | Young; Cloud only; few formats; no provenance story. | |
| 26 |
| **Artifactory, Nexus** | Every format; proxies and caches; enterprise features. | Costly; heavy JVM operations; slow UIs. A second system beside the forge, with its own identities and permissions. Provenance and scanning are add-on products. | |
| 27 |
| **Harbor, Quay** | Self-hosted OCI with signing (cosign, Notation), replication and scanning. | Many components to run. A central bottleneck: peer-to-peer means adding Dragonfly. | |
| 28 |
| **ECR, Artifact Registry, ACR** | Cloud IAM; regional; scanning; lazy pulling on AWS (SOCI). | Lock-in; egress costs; not sovereign. | |
| 29 |
|
| 30 |
And the bolt-ons each of them needs: |
| 31 |
|
| 32 |
- **Distribution:** Dragonfly, Kraken or Spegel, run beside the registry. |
| 33 |
- **Lazy pulling:** eStargz, SOCI, Nydus or zstd:chunked, each a format |
| 34 |
plus a snapshotter. It matters: pulling took 76% of container start time |
| 35 |
while only 6.4% of the pulled data was read (Slacker, FAST '16). |
| 36 |
- **Signing and provenance:** cosign and Rekor, SLSA generators, |
| 37 |
in-toto, Notation, attached afterwards through the OCI referrers API. |
| 38 |
- **Retention:** cleanup policies plus a separate garbage collection. |
| 39 |
|
| 40 |
## 2. The pattern behind the weaknesses |
| 41 |
|
| 42 |
1. **The unit is the tarball layer.** One changed file re-pushes and |
| 43 |
re-pulls a whole layer; layers share nothing below their own size; and a |
| 44 |
layer has two digests (compressed and not), so tools disagree on what |
| 45 |
it is called. |
| 46 |
2. **Names are mutable pointers.** A tag moves silently and leaves no |
| 47 |
history. Pinning by digest is safe but unreadable, so most people |
| 48 |
don't. |
| 49 |
3. **Trust is bolted on afterwards.** The registry stores bytes; a |
| 50 |
separate tool signs them, a third says how they were built, and each |
| 51 |
brings its own keys and identities. Nothing stops an unsigned image |
| 52 |
from being pushed, and most pulls never verify anything. |
| 53 |
4. **Distribution is someone else's problem.** Every registry is a |
| 54 |
central server; peer-to-peer and lazy loading are extra products. |
| 55 |
5. **The forge and the registry are strangers.** Permissions, identity, |
| 56 |
retention and "which commit is this?" are kept twice and drift apart. |
| 57 |
|
| 58 |
## 3. What Oxygen already has |
| 59 |
|
| 60 |
Every piece Cylinder needs exists in some form: |
| 61 |
|
| 62 |
- **Content-defined chunks with BLAKE3** (FastCDC, 32 to 256 KiB), shared |
| 63 |
by repositories, release assets, and runner images: deduplication below |
| 64 |
file level, across everything. |
| 65 |
- **Hybrid post-quantum signatures** (Ed25519 with ML-DSA-65), identities |
| 66 |
with delegation chains, and rotation recorded in the repository. |
| 67 |
- **Signed Flow receipts:** a runner signs what it built, from which |
| 68 |
state, with which pipeline and environment, producing which outputs. |
| 69 |
That is provenance by construction, not a statement added later. |
| 70 |
- **flow-casfs:** a content-addressed tree mounted lazily over FUSE or |
| 71 |
virtio-fs. Lazy pulling is native, not a format bolted on. |
| 72 |
- **The Jetstream swarm:** peer-to-peer chunks, rarest first, TLS pinned |
| 73 |
by the hub, tickets that only the hub signs. |
| 74 |
- **Strata:** storage on disk, S3, GCS or Azure, with no server in between, |
| 75 |
sovereign keys and client-side hash verification. |
| 76 |
- **OxygenHub:** one identity and permission model, checks, proposals and |
| 77 |
reports. |
| 78 |
- **Scoped delegation:** key scopes (`changes`, `refs`, `seal`) allowing |
| 79 |
principals and AI agents to act within strict cryptographic boundaries. |
| 80 |
|
| 81 |
## 4. The design |
| 82 |
|
| 83 |
### 4.1 One kind of thing: a tree of chunks |
| 84 |
|
| 85 |
An **artifact** is an immutable, content-addressed object in the Oxygen |
| 86 |
store naming a file tree: |
| 87 |
|
| 88 |
``` |
| 89 |
Artifact { |
| 90 |
version [0]: 1 |
| 91 |
kind [1]: "container" | "runner-image" | "package" | "archive" | "report" |
| 92 |
arch [2]: text | null // "x86_64", "riscv64", "aarch64" |
| 93 |
os [3]: text | null // "linux", "darwin", "none" |
| 94 |
tree [4]: ObjectRef // DirTree blob: paths, modes, blobs, sizes |
| 95 |
entrypoint [5]: [text] | null // default exec argv |
| 96 |
env [6]: map<text, text> | null // environment variables |
| 97 |
labels [7]: map<text, text> | null // metadata / OCI labels |
| 98 |
total_size [8]: u64 // uncompressed bytes |
| 99 |
receipt [9]: ObjectRef | null // Receipt object that produced it |
| 100 |
} |
| 101 |
``` |
| 102 |
|
| 103 |
Files in the `DirTree` are ordinary Oxygen `blob` objects composed of |
| 104 |
FastCDC BLAKE3 `chunk`s. So: |
| 105 |
|
| 106 |
- An image rebuilt with one changed file moves only that file's changed |
| 107 |
chunks: no layers. |
| 108 |
- A release tarball, an npm package, and the container image containing |
| 109 |
the same binary share its chunks verbatim. If a 100 MB model or library |
| 110 |
is committed in the repo and then packaged, zero new chunk bytes are |
| 111 |
written to Strata. |
| 112 |
- A single file can be fetched independently: a container or job starts |
| 113 |
after fetching only the files it reads. |
| 114 |
- Every artifact has exactly one name for its content: the BLAKE3 of its |
| 115 |
canonical CBOR encoding. |
| 116 |
- **MicroVMs:** Flow's MicroVM tier runs on Firecracker, which has no |
| 117 |
virtio-fs, so a microVM gets the artifact as a block device: an ext4 |
| 118 |
image served on the fly from the chunk tree (a userspace block device, |
| 119 |
ublk, or an NBD server), fetching blocks as the guest reads them and |
| 120 |
duplicating no bytes. That removes copying whole images onto each |
| 121 |
runner's disk without changing hypervisor. The syscall tier and |
| 122 |
containers mount the tree directly over `flow-casfs`; mounting it with |
| 123 |
virtio-fs would need Cloud Hypervisor or QEMU instead of Firecracker. |
| 124 |
|
| 125 |
### 4.2 Names are signed histories, not pointers |
| 126 |
|
| 127 |
A **channel** (`app:stable`, `runner-image:rust`, `pkg:cargo/my-crate`) is an |
| 128 |
append-only log of signed moves, recorded as an attestation of kind |
| 129 |
`channel`: |
| 130 |
|
| 131 |
``` |
| 132 |
Attest (kind = "channel") { |
| 133 |
channel [0]: text // e.g. "app:stable" |
| 134 |
old_art [1]: ObjectRef | null // previous artifact hash (CAS check) |
| 135 |
new_art [2]: ObjectRef // target artifact hash |
| 136 |
serial [3]: u64 // monotonically increasing |
| 137 |
receipt [4]: ObjectRef | null // Receipt that built new_art |
| 138 |
reason [5]: text // commit message or promotion note |
| 139 |
} |
| 140 |
``` |
| 141 |
|
| 142 |
A move needs policy: signed by an authorized key under the repository's |
| 143 |
trust policy (`spec.md §14.5`), from an approved bookmark, after |
| 144 |
required checks or human approvals pass. Clients follow a channel and |
| 145 |
verify each step: |
| 146 |
|
| 147 |
- A tag cannot move silently. The tj-actions attack needed one stolen |
| 148 |
token; here it needs a move signed by an authorized key, and the move |
| 149 |
is logged in the append-only history. |
| 150 |
- Rolling back is a move to an earlier artifact, recorded and attested |
| 151 |
like any other move. |
| 152 |
- Pinning stays readable and tamper-evident: `app:stable@42` names the |
| 153 |
42nd entry of an audited log. |
| 154 |
- On Strata stores, updates use compare-and-swap on the channel record |
| 155 |
(`old_art` and `serial`), matching how bookmark `refs` operate. |
| 156 |
|
| 157 |
### 4.3 Provenance by construction and multi-party consensus |
| 158 |
|
| 159 |
An artifact is published by a Flow job, and its record is the unbroken |
| 160 |
chain: |
| 161 |
|
| 162 |
> source state → pipeline → runner (identity, tier, image) → signed |
| 163 |
> receipt → artifact |
| 164 |
|
| 165 |
Each link is a hash or signature verifiable offline. Beyond what |
| 166 |
attestations give today: |
| 167 |
|
| 168 |
- **Assurance levels:** a MicroVM job on a registered runner is worth |
| 169 |
more than a syscall-tier job, which is worth more than a signed local |
| 170 |
build. Trust policies can enforce minimum assurance floors for |
| 171 |
sensitive channels. |
| 172 |
- **Multi-party consensus (`reproduced`):** when multiple independent |
| 173 |
runners on disjoint machines or architectures build the same artifact |
| 174 |
hash from the same state, each signs a receipt. A policy rule can |
| 175 |
require threshold consensus: |
| 176 |
`require: "reproduced(2)"` demands two independent runner receipts |
| 177 |
before an artifact can be promoted to a production channel. |
| 178 |
- **Upstream lineage:** an artifact built from other artifacts (a base |
| 179 |
image, a package) names their hashes in its receipt inputs, so the chain |
| 180 |
reaches back transitively to their sources. |
| 181 |
- **An SBOM with every artifact:** the job that publishes an artifact also |
| 182 |
records what it contains by package and version (an SBOM, itself an |
| 183 |
artifact named in the receipt's outputs), so "which artifacts hold a |
| 184 |
vulnerable version of this package" is a lookup, not a scan (§4.6). |
| 185 |
|
| 186 |
### 4.4 Distribution is built in |
| 187 |
|
| 188 |
- **The store is the seed; the swarm delivers.** Jetstream's swarm, |
| 189 |
generalized: any runner, cluster node or developer machine in a site |
| 190 |
shares the chunks it has, over TLS pinned by the hub, admitted by |
| 191 |
hub-signed tickets. |
| 192 |
- **Lazy by default:** artifacts are mounted via `flow-casfs` and fetched |
| 193 |
on demand. |
| 194 |
- **Learned prefetch:** each run records which chunks were accessed |
| 195 |
during its initial execution window; subsequent pulls request those |
| 196 |
chunks first. |
| 197 |
- **Sites and air gaps:** a site syncs chunks and channel logs from |
| 198 |
another hub as a signed bundle, so a disconnected network gets |
| 199 |
verified artifacts with no live connection. |
| 200 |
|
| 201 |
### 4.5 Sovereign storage, and our own transparency |
| 202 |
|
| 203 |
- Bytes live on any Strata store: local disk, S3, GCS or Azure, in the |
| 204 |
customer's own account and region, encrypted at rest by the store with |
| 205 |
customer-managed keys (for example S3 with a KMS key). Chunks stay named |
| 206 |
by the hash of their plain bytes: encrypting them on the client with |
| 207 |
separate keys would stop them deduplicating and stop peers checking them |
| 208 |
by hash. |
| 209 |
- Channel moves and signatures are recorded in the hub's append-only |
| 210 |
Merkle log. Auditing requires no dependence on public ledgers like |
| 211 |
Rekor, though Cylinder can bridge to them when desired. |
| 212 |
|
| 213 |
### 4.6 Policy at point of use, and the inverted vulnerability index |
| 214 |
|
| 215 |
Verification happens where the artifact is used, not only where it is |
| 216 |
stored: |
| 217 |
|
| 218 |
- A runner checks the job's image against policy before execution. |
| 219 |
- A Kubernetes admission hook checks pod images against channel policies |
| 220 |
and assurance levels. |
| 221 |
- `oxy` and `flow` verify what they fetch. |
| 222 |
|
| 223 |
**The inverted file index:** Because artifacts are trees of |
| 224 |
content-addressed files rather than opaque tarballs, Cylinder keeps an |
| 225 |
index from each file's hash to the artifacts that contain it: |
| 226 |
|
| 227 |
$$\text{FileHash} \longrightarrow [\text{ArtifactHash}]$$ |
| 228 |
|
| 229 |
- **Exact files, at once.** Given a known-bad file (a compromised binary, |
| 230 |
a leaked key, the exact `libcrypto.so.3` an advisory names), one lookup |
| 231 |
lists every container image, runner image and package across the fleet |
| 232 |
holding it, with no layers to unpack. |
| 233 |
- **Not versions.** A patched or rebuilt library is a different file with |
| 234 |
a different hash, so this index does not answer "which images have a |
| 235 |
vulnerable version of OpenSSL". That comes from package identity: the |
| 236 |
SBOM each artifact carries (§4.3), indexed by package and version. The |
| 237 |
two together answer both questions; the file index alone answers the |
| 238 |
first. |
| 239 |
- **Revocation without an outage.** When an artifact, base image or file |
| 240 |
is revoked in policy, admission hooks refuse new pods that use it at |
| 241 |
once. Pods already running are not touched by admission; a controller |
| 242 |
marks them, then replaces or evicts them within a grace period the |
| 243 |
policy sets, so one policy change cannot take production down. |
| 244 |
|
| 245 |
### 4.7 Retention by reachability and deployment leases |
| 246 |
|
| 247 |
What a channel, a release, a pinned pipeline or an active deployment |
| 248 |
reaches is kept; unreferenced chunks are collected by `oxy gc`. |
| 249 |
|
| 250 |
**Deployment leases:** Container registries often suffer from garbage |
| 251 |
collection deleting images still running on long-lived cluster pods. |
| 252 |
To prevent this, nodes or Kubernetes operators register periodic |
| 253 |
**deployment leases**: |
| 254 |
|
| 255 |
``` |
| 256 |
Attest (kind = "lease") { |
| 257 |
artifact [0]: ObjectRef // artifact hash currently running |
| 258 |
scope [1]: text // e.g. "k8s:cluster-lon/ns/workload" |
| 259 |
expires [2]: u64 // timestamp |
| 260 |
} |
| 261 |
``` |
| 262 |
|
| 263 |
Active leases act as garbage collection roots. If a workload terminates |
| 264 |
or a cluster is decommissioned, the lease expires naturally, enabling |
| 265 |
safe automated retention without operational risk. A lease is signed by |
| 266 |
a key scoped to leases and to its `scope`, and has a maximum length (a |
| 267 |
day, renewed while the workload runs), so a compromised node cannot hold |
| 268 |
storage forever or pin artifacts outside its own scope. |
| 269 |
|
| 270 |
### 4.8 Compatibility at the edge |
| 271 |
|
| 272 |
- **An OCI registry façade:** `docker pull` and Kubernetes work |
| 273 |
unchanged. Manifests and layers are assembled on the fly from chunks. |
| 274 |
`docker push` is accepted and chunked via FastCDC, applying |
| 275 |
deduplication to legacy images. |
| 276 |
- **A containerd snapshotter** speaking the native protocol, mounting |
| 277 |
file trees via `flow-casfs` for instant sub-second container starts |
| 278 |
and P2P delivery on Kubernetes. |
| 279 |
- **Package façades (Cargo, npm, PyPI):** Served from the same chunk |
| 280 |
store. Façade endpoints synthesize package metadata (Cargo sparse |
| 281 |
indexes, npm packuments, PyPI simple indexes) dynamically from artifact |
| 282 |
manifests. |
| 283 |
- **Upstream dependency quarantine:** When Flow builds fetch third-party |
| 284 |
packages from crates.io or npm, Cylinder proxies them into the |
| 285 |
organization's Strata store, locking the hashes into internal mirror |
| 286 |
channels. Upstream outages or deletions cannot break builds. Packages |
| 287 |
arrive compressed (`.crate` and `.tgz` are gzip), which chunks poorly, |
| 288 |
so the original archive is kept for clients that check its hash and its |
| 289 |
unpacked files are chunked as an artifact tree, where they deduplicate |
| 290 |
with everything else. |
| 291 |
|
| 292 |
### 4.9 AI-native agent integration |
| 293 |
|
| 294 |
Extending Oxygen's scoped delegation model (`ai_native_agent_workflows.md §2`), |
| 295 |
agent keys can hold artifact-specific scopes: |
| 296 |
|
| 297 |
``` |
| 298 |
delegates [8]: [{ key: KeyId, scope: ["changes" | "refs" | "seal" | "artifacts" | "channels"], expires: u64? }] |
| 299 |
``` |
| 300 |
|
| 301 |
- An AI agent (e.g. Aider, Claude Code) working autonomously can be |
| 302 |
granted `--scope artifacts` and restricted channel rights: |
| 303 |
- Permitted: building artifacts and moving `agents/NAME/**` channels. |
| 304 |
- Forbidden: moving production channels (`app:stable`, `release/*`). |
| 305 |
- Agents can test containerized workloads end-to-end in isolated |
| 306 |
channels, while production promotion remains guarded by human landing |
| 307 |
rules or multi-party consensus. |
| 308 |
|
| 309 |
### 4.10 Revset query integration |
| 310 |
|
| 311 |
Oxygen's revset language (`revset_query_language.md`) bridges source |
| 312 |
changes and deployed artifacts: |
| 313 |
|
| 314 |
|
| 315 |
|
| 316 |
| `artifact(CHANNEL)` | Changes contained within the artifact currently at `CHANNEL` | |
| 317 |
| `deployed(ENV)` | Changes currently active in environment `ENV` (via active leases) | |
| 318 |
| `unshipped()` | Equivalent to `main ~ artifact(app:stable)` (changes on `main` that have not reached production) | |
| 319 |
| `reproduced()` | Changes whose built artifacts have 2+ independent runner receipts | |
| 320 |
| `vulnerable(CVE)` | Changes that touch or introduced files present in an artifact flagged with `CVE` | |
| 321 |
|
| 322 |
Running `oxy log -r 'main ~ artifact(app:stable)'` immediately shows all |
| 323 |
changes pending deployment. |
| 324 |
|
| 325 |
### 4.11 The UI (inside the hub) |
| 326 |
|
| 327 |
- **Per repository, an Artifacts tab:** published artifacts, channel |
| 328 |
histories, and the provenance graph from commit to run, artifact, and |
| 329 |
deployment lease. |
| 330 |
- **Site-wide:** chunk distribution across hubs and runners, active |
| 331 |
swarm transfers, deduplication savings, and the global CVE inverted index. |
| 332 |
- **Runner images** are listed as standard artifacts with their build |
| 333 |
receipts and hardware architecture badges. |
| 334 |
|
| 335 |
```mermaid |
| 336 |
flowchart LR |
| 337 |
subgraph Build |
| 338 |
S[Source state] --> P[Pipeline] |
| 339 |
P --> R1[Runner 1: microvm] |
| 340 |
P --> R2[Runner 2: disjoint host] |
| 341 |
R1 -->|receipt 1| A[Artifact: tree of chunks] |
| 342 |
R2 -->|receipt 2| A |
| 343 |
end |
| 344 |
A -->|consensus move| C[Channel log: app:stable@42] |
| 345 |
A --> ST[(Strata store)] |
| 346 |
ST -->|seed| SW{{Swarm}} |
| 347 |
SW --> N1[Runner] |
| 348 |
SW --> N2[K8s node: containerd snapshotter] |
| 349 |
SW --> N3[Developer] |
| 350 |
N2 -->|deployment lease| DL[Active Leases: GC Roots] |
| 351 |
C -->|policy check| N2 |
| 352 |
OCI[OCI façade] --- ST |
| 353 |
PKG[Package façades: cargo/npm/pypi] --- ST |
| 354 |
H[OxygenHub UI & CVE Index] --- C |
| 355 |
H --- A |
| 356 |
``` |
| 357 |
|
| 358 |
### 4.12 Where it stands among the services |
| 359 |
|
| 360 |
- **OxygenHub** owns repositories, proposals, checks, user identity, |
| 361 |
and the UI. |
| 362 |
- **Jetstream** owns the fleet: queue, placement, and runner swarms. It |
| 363 |
fetches runner images from Cylinder by artifact hash. |
| 364 |
- **Flow** executes pipelines and produces signed execution receipts. |
| 365 |
- **Cylinder** owns artifacts: manifests, channel logs, provenance |
| 366 |
chains, distribution, package façades, and deployment leases. |
| 367 |
- **Strata** provides the underlying content-addressed chunk storage. |
| 368 |
|
| 369 |
Each service runs embedded in OxygenHub by default, or standalone for |
| 370 |
distributed scale. |
| 371 |
|
| 372 |
## 5. Better how, in one table |
| 373 |
|
| 374 |
|
| 375 |
|
| 376 |
| Rebuild with one changed file | Re-push and re-pull the layer | Only that file's changed chunks | |
| 377 |
| Dedupe | Whole identical layers | FastCDC chunks across repos, images and packages | |
| 378 |
| Names | Mutable tags; digests nobody reads | Signed, append-only channels (`app:stable@42`) | |
| 379 |
| Provenance | Attestations added later, if at all | The build's signed receipt is the record | |
| 380 |
| Signing | A separate tool and keys | Native Ed25519 + ML-DSA-65 composite signatures | |
| 381 |
| Consensus | Single-builder attestation | Multi-party consensus receipts (`reproduced(N)`) | |
| 382 |
| Lazy start | Bolted-on snapshotter (SOCI, Nydus) | Native: file trees mounted via `flow-casfs` | |
| 383 |
| Peer to peer | Separate mesh daemon (Dragonfly) | Built-in Jetstream swarm, TLS-pinned | |
| 384 |
| Vulnerability search | Unpack and scan every layer | Exact files by one index lookup; versions by SBOM package index | |
| 385 |
| Retention | Tag rules + manual GC | Reachability + active deployment leases | |
| 386 |
| AI workflows | Uncontrolled service tokens | Scoped delegation (`artifacts`, `channels`) | |
| 387 |
| Querying | Disconnected CI/CD dashboards | Unified revsets (`main ~ artifact(app:stable)`) | |
| 388 |
| Sovereignty | Complex multi-tier server stack | Single binary; customer KMS and Strata bucket | |
| 389 |
|
| 390 |
## 6. How we could build it |
| 391 |
|
| 392 |
- **C0. Images in the hub (built 2026-10-12, oxygenhub #146).** Admin › |
| 393 |
Runners › Images: every runner image, its size and architecture, where |
| 394 |
it is (the hub, each runner, swarm fetches in progress), the |
| 395 |
repositories whose jobs used it, and an image held nowhere marked. |
| 396 |
- **C1. The service boundary and artifact model.** Implement the |
| 397 |
`Artifact` CBOR object type and FastCDC chunk engine in `cylinder`; |
| 398 |
unify runner images with artifacts. |
| 399 |
- **C2. Channels, provenance and consensus.** Flow publishes artifacts; |
| 400 |
signed channel move attestations; multi-party `reproduced(N)` consensus; |
| 401 |
revset predicates. |
| 402 |
- **C3. The OCI façade and containerd snapshotter.** OCI pull/push |
| 403 |
translation; `containerd` snapshotter over `flow-casfs`. |
| 404 |
- **C4. Leases and the indexes.** Kubernetes deployment lease operator |
| 405 |
for safe GC; the inverted file index and the SBOM package index; the |
| 406 |
controller that replaces revoked running workloads within their grace |
| 407 |
period. |
| 408 |
- **C5. Reach and package façades.** Package façades for Cargo, npm, |
| 409 |
and PyPI; upstream dependency quarantine mirroring. |
| 410 |
|
| 411 |
## 7. Open questions |
| 412 |
|
| 413 |
These are Andy's decisions. Each carries the position proposed in review, |
| 414 |
not an answer. |
| 415 |
|
| 416 |
- **The name.** Proposed: keep **Cylinder**. It fits the elemental |
| 417 |
progression: *Oxygen* (VCS) is carried in *Cylinders* (artifacts) by |
| 418 |
*Jetstream* (the fleet), driven by *Flow* (pipelines). |
| 419 |
- **Where it lives.** Proposed: first in `oxygenflow` as |
| 420 |
`crates/oxygen-cylinder`, beside `jet` and `flow`, to share `flow-casfs` |
| 421 |
and the chunking code; its own repository from C3. |
| 422 |
- **Local builds.** Proposed: allowed, at the lowest assurance level |
| 423 |
(`local`); production channels require more (`tier-microvm` and |
| 424 |
`reproduced(2)`). |
| 425 |
- **A public catalogue and federation.** Proposed: public hubs federate |
| 426 |
by publishing signed Strata bundles of channels and artifacts, checked |
| 427 |
offline with no central intermediary. |
| 428 |
- **Its place in the queue.** Proposed: C1 alongside Jetstream's service |
| 429 |
boundary, so Jetstream's image store is built on Cylinder from the |
| 430 |
start rather than on a temporary mechanism. Against: the core work of |
| 431 |
2026-10-03 (Strata's remaining stores, agents, hub polish) waits |
| 432 |
longer. |
| 433 |
|
| 434 |
## Sources |
| 435 |
|
| 436 |
- Docker Hub pull limits: [docs.docker.com, pull usage and limits](https://docs.docker.com/docker-hub/usage/pulls/); [heise, "only 10 pulls per hour"](https://heise.de/-10329047) |
| 437 |
- GHCR billing: [GitHub community discussion #183054](https://github.com/orgs/community/discussions/183054) |
| 438 |
- GitLab cleanup policies and garbage collection: [GitLab docs, reduce container registry storage](https://docs.gitlab.com/user/packages/container_registry/reduce_container_registry_storage/) |
| 439 |
- Bitbucket Packages: [Atlassian blog, Bitbucket Packages](https://www.atlassian.com/blog/bitbucket/bitbucket-packages-container-registry); [GA announcement](https://www.atlassian.com/blog/bitbucket/open-beta-introducing-packages-native-container-registry-in-bitbucket-cloud) |
| 440 |
- Pull time versus data read: [SOCI paper (Amazon), citing Slacker](https://cdn.amazon.science/84/96/51ea2e0d40fca8cfaee9328278b9/soci-paper.pdf); [Grab engineering, lazy loading](https://engineering.grab.com/docker-lazy-loading) |
| 441 |
- zstd:chunked: [Red Hat, faster container image pulls](https://www.redhat.com/sysadmin/faster-container-image-pulls) |
| 442 |
- Peer-to-peer meshes: [Spegel vs Kraken vs Dragonfly](https://www.pistack.xyz/posts/2026-04-28-spegel-vs-kraken-vs-dragonfly-self-hosted-p2p-container-image-distribution-guide-2026/) |
| 443 |
- Sigstore in registries: [Sigstore in image registries: adoption and reality](https://jacar.es/en/sigstore-in-image-registries-adoption-and-reality/); [cosign and the referrers API](https://oneuptime.com/blog/post/2026-08-11-cosign-signature-storage-oci-referrers-separate-repositories/markdown) |
| 444 |
- tj-actions tag attack: [CVE-2025-30066](https://inventivehq.com/tools/security/cve-lookup/cve-2025-30066); [Legit Security, immutable tags](https://legitsecurity.com/blog/what-are-immutable-tags-and-can-they-protect-you-from-supply-chain-attacks) |
| 445 |
- Artifactory and Nexus: [Codefresh, Artifactory alternatives](https://codefresh.io/learn/jfrog-artifactory/top-8-artifactory-alternatives-in-2025); [PeerSpot, Artifactory improvements](https://origin.peerspot.com/questions/what-needs-improvement-with-jfrog-artifactory) |