OXYGENHUB
# 284fe47d98b9 oxygen / docs / cylinder.md
22.6 KiB · Markdown · last changed Cylinder C1, first part: artifacts as trees of the repository's chunks, oxy artifact pack/list/show/unpack/verify, kept by gc (refs #108) 7 days ago renderedsource blame history raw
1
# Cylinder: artifacts, the Oxygen way
2
3
Status: Draft 0.2 (proposal revision 2), 2026-10-12. Incorporates review of
4
object model, deployment leases, inverted vulnerability indexing,
5
multi-party consensus, agent scopes, and revset queries. Not built.
6
"Cylinder" is a working name (the artifact-repository idea parked on
7
2026-10-02); nothing here is decided until Andy says so.
8
9
**Cylinder** keeps and moves everything a build produces: runner images,
10
container images, packages, binaries, SBOMs and reports. It would be a
11
service of its own, embedded in OxygenHub by default and shown through
12
the hub's UI, the same pattern as Jetstream. It is meant to be better
13
than what exists, not compatible with it; compatibility is an edge we add
14
for adoption, never the core.
15
16
## 1. What exists today
17
18
| | Strengths | Weaknesses |
19
|---|---|---|
20
| **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. |
21
| **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. |
22
| **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. |
23
| **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. |
24
| **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. |
25
| **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. |
26
| **ECR, Artifact Registry, ACR** | Cloud IAM; regional; scanning; lazy pulling on AWS (SOCI). | Lock-in; egress costs; not sovereign. |
27
28
And the bolt-ons each of them needs:
29
30
- **Distribution:** Dragonfly, Kraken or Spegel, run beside the registry.
31
- **Lazy pulling:** eStargz, SOCI, Nydus or zstd:chunked, each a format
32
  plus a snapshotter. It matters: pulling took 76% of container start time
33
  while only 6.4% of the pulled data was read (Slacker, FAST '16).
34
- **Signing and provenance:** cosign and Rekor, SLSA generators,
35
  in-toto, Notation, attached afterwards through the OCI referrers API.
36
- **Retention:** cleanup policies plus a separate garbage collection.
37
38
## 2. The pattern behind the weaknesses
39
40
1. **The unit is the tarball layer.** One changed file re-pushes and
41
   re-pulls a whole layer; layers share nothing below their own size; and a
42
   layer has two digests (compressed and not), so tools disagree on what
43
   it is called.
44
2. **Names are mutable pointers.** A tag moves silently and leaves no
45
   history. Pinning by digest is safe but unreadable, so most people
46
   don't.
47
3. **Trust is bolted on afterwards.** The registry stores bytes; a
48
   separate tool signs them, a third says how they were built, and each
49
   brings its own keys and identities. Nothing stops an unsigned image
50
   from being pushed, and most pulls never verify anything.
51
4. **Distribution is someone else's problem.** Every registry is a
52
   central server; peer-to-peer and lazy loading are extra products.
53
5. **The forge and the registry are strangers.** Permissions, identity,
54
   retention and "which commit is this?" are kept twice and drift apart.
55
56
## 3. What Oxygen already has
57
58
Every piece Cylinder needs exists in some form:
59
60
- **Content-defined chunks with BLAKE3** (FastCDC, 32 to 256 KiB), shared
61
  by repositories, release assets, and runner images: deduplication below
62
  file level, across everything.
63
- **Hybrid post-quantum signatures** (Ed25519 with ML-DSA-65), identities
64
  with delegation chains, and rotation recorded in the repository.
65
- **Signed Flow receipts:** a runner signs what it built, from which
66
  state, with which pipeline and environment, producing which outputs.
67
  That is provenance by construction, not a statement added later.
68
- **flow-casfs:** a content-addressed tree mounted lazily over FUSE or
69
  virtio-fs. Lazy pulling is native, not a format bolted on.
70
- **The Jetstream swarm:** peer-to-peer chunks, rarest first, TLS pinned
71
  by the hub, tickets that only the hub signs.
72
- **Strata:** storage on disk, S3, GCS or Azure, with no server in between,
73
  sovereign keys and client-side hash verification.
74
- **OxygenHub:** one identity and permission model, checks, proposals and
75
  reports.
76
- **Scoped delegation:** key scopes (`changes`, `refs`, `seal`) allowing
77
  principals and AI agents to act within strict cryptographic boundaries.
78
79
## 4. The design
80
81
### 4.1 One kind of thing: a tree of chunks
82
83
An **artifact** is an immutable, content-addressed object in the Oxygen
84
store naming a file tree:
85
86
```
87
Artifact {
88
  version     [0]: 1
89
  kind        [1]: "container" | "runner-image" | "package" | "archive" | "report"
90
  arch        [2]: text | null              // "x86_64", "riscv64", "aarch64"
91
  os          [3]: text | null              // "linux", "darwin", "none"
92
  tree        [4]: ObjectRef                // DirTree blob: paths, modes, blobs, sizes
93
  entrypoint  [5]: [text] | null            // default exec argv
94
  env         [6]: map<text, text> | null   // environment variables
95
  labels      [7]: map<text, text> | null   // metadata / OCI labels
96
  total_size  [8]: u64                      // uncompressed bytes
97
  receipt     [9]: ObjectRef | null         // Receipt object that produced it
98
}
99
```
100
101
Files in the `DirTree` are ordinary Oxygen `blob` objects composed of
102
FastCDC BLAKE3 `chunk`s. So:
103
104
- An image rebuilt with one changed file moves only that file's changed
105
  chunks: no layers.
106
- A release tarball, an npm package, and the container image containing
107
  the same binary share its chunks verbatim. If a 100 MB model or library
108
  is committed in the repo and then packaged, zero new chunk bytes are
109
  written to Strata.
110
- A single file can be fetched independently: a container or job starts
111
  after fetching only the files it reads.
112
- Every artifact has exactly one name for its content: the BLAKE3 of its
113
  canonical CBOR encoding.
114
- **MicroVMs and virtio-fs:** Jetstream microVM runners mount an artifact's
115
  tree directly via virtio-fs over `flow-casfs`, eliminating ext4 block
116
  images, loop devices, and cold disk allocation. Where a raw block device
117
  is required by legacy hardware or external hypervisors, an ext4 block
118
  map is synthesized on the fly from the chunk tree without duplicating
119
  bytes.
120
121
### 4.2 Names are signed histories, not pointers
122
123
A **channel** (`app:stable`, `runner-image:rust`, `pkg:cargo/my-crate`) is an
124
append-only log of signed moves, recorded as an attestation of kind
125
`channel`:
126
127
```
128
Attest (kind = "channel") {
129
  channel   [0]: text             // e.g. "app:stable"
130
  old_art   [1]: ObjectRef | null // previous artifact hash (CAS check)
131
  new_art   [2]: ObjectRef        // target artifact hash
132
  serial    [3]: u64              // monotonically increasing
133
  receipt   [4]: ObjectRef | null // Receipt that built new_art
134
  reason    [5]: text             // commit message or promotion note
135
}
136
```
137
138
A move needs policy: signed by an authorized key under the repository's
139
trust policy (`spec.md §14.5`), from an approved bookmark, after
140
required checks or human approvals pass. Clients follow a channel and
141
verify each step:
142
143
- A tag cannot move silently. The tj-actions attack needed one stolen
144
  token; here it needs a move signed by an authorized key, and the move
145
  is logged in the append-only history.
146
- Rolling back is a move to an earlier artifact, recorded and attested
147
  like any other move.
148
- Pinning stays readable and tamper-evident: `app:stable@42` names the
149
  42nd entry of an audited log.
150
- On Strata stores, updates use compare-and-swap on the channel record
151
  (`old_art` and `serial`), matching how bookmark `refs` operate.
152
153
### 4.3 Provenance by construction and multi-party consensus
154
155
An artifact is published by a Flow job, and its record is the unbroken
156
chain:
157
158
> source state → pipeline → runner (identity, tier, image) → signed
159
> receipt → artifact
160
161
Each link is a hash or signature verifiable offline. Beyond what
162
attestations give today:
163
164
- **Assurance levels:** a MicroVM job on a registered runner is worth
165
  more than a syscall-tier job, which is worth more than a signed local
166
  build. Trust policies can enforce minimum assurance floors for
167
  sensitive channels.
168
- **Multi-party consensus (`reproduced`):** when multiple independent
169
  runners on disjoint machines or architectures build the same artifact
170
  hash from the same state, each signs a receipt. A policy rule can
171
  require threshold consensus:
172
  `require: "reproduced(2)"` demands two independent runner receipts
173
  before an artifact can be promoted to a production channel.
174
- **Upstream lineage:** an artifact built from other artifacts (a base
175
  image, a package) names their hashes in its receipt inputs, so the chain
176
  reaches back transitively to their sources.
177
178
### 4.4 Distribution is built in
179
180
- **The store is the seed; the swarm delivers.** Jetstream's swarm,
181
  generalized: any runner, cluster node or developer machine in a site
182
  shares the chunks it has, over TLS pinned by the hub, admitted by
183
  hub-signed tickets.
184
- **Lazy by default:** artifacts are mounted via `flow-casfs` and fetched
185
  on demand.
186
- **Learned prefetch:** each run records which chunks were accessed
187
  during its initial execution window; subsequent pulls request those
188
  chunks first.
189
- **Sites and air gaps:** a site syncs chunks and channel logs from
190
  another hub as a signed bundle, so a disconnected network gets
191
  verified artifacts with no live connection.
192
193
### 4.5 Sovereign storage, and our own transparency
194
195
- Bytes live on any Strata store: local disk, S3, GCS or Azure, in the
196
  customer's own account and region, encrypted with customer-managed KMS.
197
- Channel moves and signatures are recorded in the hub's append-only
198
  Merkle log. Auditing requires no dependence on public ledgers like
199
  Rekor, though Cylinder can bridge to them when desired.
200
201
### 4.6 Policy at point of use, and the inverted vulnerability index
202
203
Verification happens where the artifact is used, not only where it is
204
stored:
205
206
- A runner checks the job's image against policy before execution.
207
- A Kubernetes admission hook checks pod images against channel policies
208
  and assurance levels.
209
- `oxy` and `flow` verify what they fetch.
210
211
**The global inverted vulnerability index:** Because artifacts are trees
212
of content-addressed files and chunks rather than opaque tarballs,
213
Cylinder maintains an inverted index from chunk/file hash to artifacts:
214
215
$$\text{FileHash} \longrightarrow [\text{ArtifactHash}]$$
216
217
When a security advisory or zero-day is disclosed for a binary or
218
library (e.g., a vulnerable `libcrypto.so.3` or Go binary hash):
219
- Cylinder performs an immediate $O(1)$ lookup to identify every
220
  container image, runner image, and package across the fleet containing
221
  that file.
222
- There is no need to unpack gigabytes of tarball layers across thousands
223
  of images to discover exposure.
224
- **Continuous revocation:** If an upstream library or base image is
225
  revoked in policy, point-of-use admission hooks immediately block
226
  downstream pods from running any artifact containing that chunk,
227
  eliminating the gap between vulnerability discovery and redeployment.
228
229
### 4.7 Retention by reachability and deployment leases
230
231
What a channel, a release, a pinned pipeline or an active deployment
232
reaches is kept; unreferenced chunks are collected by `oxy gc`.
233
234
**Deployment leases:** Container registries often suffer from garbage
235
collection deleting images still running on long-lived cluster pods.
236
To prevent this, nodes or Kubernetes operators register periodic
237
**deployment leases**:
238
239
```
240
Attest (kind = "lease") {
241
  artifact [0]: ObjectRef  // artifact hash currently running
242
  scope    [1]: text       // e.g. "k8s:cluster-lon/ns/workload"
243
  expires  [2]: u64        // timestamp
244
}
245
```
246
247
Active leases act as garbage collection roots. If a workload terminates
248
or a cluster is decommissioned, the lease expires naturally, enabling
249
safe automated retention without operational risk.
250
251
### 4.8 Compatibility at the edge
252
253
- **An OCI registry façade:** `docker pull` and Kubernetes work
254
  unchanged. Manifests and layers are assembled on the fly from chunks.
255
  `docker push` is accepted and chunked via FastCDC, applying
256
  deduplication to legacy images.
257
- **A containerd snapshotter** speaking the native protocol, mounting
258
  file trees via `flow-casfs` for instant sub-second container starts
259
  and P2P delivery on Kubernetes.
260
- **Package façades (Cargo, npm, PyPI):** Served from the same chunk
261
  store. Façade endpoints synthesize package metadata (Cargo sparse
262
  indexes, npm packuments, PyPI simple indexes) dynamically from artifact
263
  manifests.
264
- **Upstream dependency quarantine:** When Flow builds fetch third-party
265
  packages from crates.io or npm, Cylinder proxies and chunks them into
266
  the organization's Strata store, locking the hashes into internal
267
  mirror channels. Upstream outages or deletions cannot break builds.
268
269
### 4.9 AI-native agent integration
270
271
Extending Oxygen's scoped delegation model (`ai_native_agent_workflows.md §2`),
272
agent keys can hold artifact-specific scopes:
273
274
```
275
delegates [8]: [{ key: KeyId, scope: ["changes" | "refs" | "seal" | "artifacts" | "channels"], expires: u64? }]
276
```
277
278
- An AI agent (e.g. Aider, Claude Code) working autonomously can be
279
  granted `--scope artifacts` and restricted channel rights:
280
  - Permitted: building artifacts and moving `agents/NAME/**` channels.
281
  - Forbidden: moving production channels (`app:stable`, `release/*`).
282
- Agents can test containerized workloads end-to-end in isolated
283
  channels, while production promotion remains guarded by human landing
284
  rules or multi-party consensus.
285
286
### 4.10 Revset query integration
287
288
Oxygen's revset language (`revset_query_language.md`) bridges source
289
changes and deployed artifacts:
290
291
| Expression | Selects |
292
|---|---|
293
| `artifact(CHANNEL)` | Changes contained within the artifact currently at `CHANNEL` |
294
| `deployed(ENV)` | Changes currently active in environment `ENV` (via active leases) |
295
| `unshipped()` | Equivalent to `main ~ artifact(app:stable)` (changes on `main` that have not reached production) |
296
| `reproduced()` | Changes whose built artifacts have 2+ independent runner receipts |
297
| `vulnerable(CVE)` | Changes that touch or introduced files present in an artifact flagged with `CVE` |
298
299
Running `oxy log -r 'main ~ artifact(app:stable)'` immediately shows all
300
changes pending deployment.
301
302
### 4.11 The UI (inside the hub)
303
304
- **Per repository, an Artifacts tab:** published artifacts, channel
305
  histories, and the provenance graph from commit to run, artifact, and
306
  deployment lease.
307
- **Site-wide:** chunk distribution across hubs and runners, active
308
  swarm transfers, deduplication savings, and the global CVE inverted index.
309
- **Runner images** are listed as standard artifacts with their build
310
  receipts and hardware architecture badges.
311
312
```mermaid
313
flowchart LR
314
  subgraph Build
315
    S[Source state] --> P[Pipeline]
316
    P --> R1[Runner 1: microvm]
317
    P --> R2[Runner 2: disjoint host]
318
    R1 -->|receipt 1| A[Artifact: tree of chunks]
319
    R2 -->|receipt 2| A
320
  end
321
  A -->|consensus move| C[Channel log: app:stable@42]
322
  A --> ST[(Strata store)]
323
  ST -->|seed| SW{{Swarm}}
324
  SW --> N1[Runner]
325
  SW --> N2[K8s node: containerd snapshotter]
326
  SW --> N3[Developer]
327
  N2 -->|deployment lease| DL[Active Leases: GC Roots]
328
  C -->|policy check| N2
329
  OCI[OCI façade] --- ST
330
  PKG[Package façades: cargo/npm/pypi] --- ST
331
  H[OxygenHub UI & CVE Index] --- C
332
  H --- A
333
```
334
335
### 4.12 Where it stands among the services
336
337
- **OxygenHub** owns repositories, proposals, checks, user identity,
338
  and the UI.
339
- **Jetstream** owns the fleet: queue, placement, and runner swarms. It
340
  fetches runner images from Cylinder by artifact hash.
341
- **Flow** executes pipelines and produces signed execution receipts.
342
- **Cylinder** owns artifacts: manifests, channel logs, provenance
343
  chains, distribution, package façades, and deployment leases.
344
- **Strata** provides the underlying content-addressed chunk storage.
345
346
Each service runs embedded in OxygenHub by default, or standalone for
347
distributed scale.
348
349
## 5. Better how, in one table
350
351
| | Today's best | Cylinder |
352
|---|---|---|
353
| Rebuild with one changed file | Re-push and re-pull the layer | Only that file's changed chunks |
354
| Dedupe | Whole identical layers | FastCDC chunks across repos, images and packages |
355
| Names | Mutable tags; digests nobody reads | Signed, append-only channels (`app:stable@42`) |
356
| Provenance | Attestations added later, if at all | The build's signed receipt is the record |
357
| Signing | A separate tool and keys | Native Ed25519 + ML-DSA-65 composite signatures |
358
| Consensus | Single-builder attestation | Multi-party consensus receipts (`reproduced(N)`) |
359
| Lazy start | Bolted-on snapshotter (SOCI, Nydus) | Native: file trees mounted via `flow-casfs` |
360
| Peer to peer | Separate mesh daemon (Dragonfly) | Built-in Jetstream swarm, TLS-pinned |
361
| Vulnerability search | Unpack and scan every layer ($O(N)$) | $O(1)$ inverted index by file and chunk hash |
362
| Retention | Tag rules + manual GC | Reachability + active deployment leases |
363
| AI workflows | Uncontrolled service tokens | Scoped delegation (`artifacts`, `channels`) |
364
| Querying | Disconnected CI/CD dashboards | Unified revsets (`main ~ artifact(app:stable)`) |
365
| Sovereignty | Complex multi-tier server stack | Single binary; customer KMS and Strata bucket |
366
367
## 6. How we could build it
368
369
- **C0. Images in the hub, now.** A Flow › Images page: each image, where
370
  it is (hub, runners, swarm activity) and what pins it.
371
- **C1. The service boundary and artifact model.** Implement the
372
  `Artifact` CBOR object type and FastCDC chunk engine in `cylinder`;
373
  unify runner images with artifacts.
374
  *Started 2026-10-28 (oxygen#108):* `crates/cylinder` has the
375
  `Artifact` and `DirTree` encodings of §4.1 (canonical CBOR; a name is
376
  the BLAKE3 of the encoding; `env` and `labels` as sorted pairs, since
377
  the profile's maps take integer keys), packing a directory into the
378
  repository's own blobs and chunks, verifying and unpacking, and the
379
  roots garbage collection keeps; `oxy artifact pack | list | show |
380
  unpack | verify`. Until the artifact is an object type of its own, its
381
  encoding and tree are blobs and `.oxy/artifacts/NAME` records it. Next:
382
  the object type, then runner images as artifacts.
383
- **C2. Channels, provenance and consensus.** Flow publishes artifacts;
384
  signed channel move attestations; multi-party `reproduced(N)` consensus;
385
  revset predicates.
386
- **C3. The OCI façade and containerd snapshotter.** OCI pull/push
387
  translation; `containerd` snapshotter over `flow-casfs`.
388
- **C4. Leases and the inverted index.** Kubernetes deployment lease
389
  operator for safe GC; global inverted chunk index for instantaneous
390
  CVE discovery.
391
- **C5. Reach and package façades.** Package façades for Cargo, npm,
392
  and PyPI; upstream dependency quarantine mirroring.
393
394
## 7. Open questions
395
396
- **The name:** Keep **Cylinder**. It fits the elemental progression:
397
  *Oxygen* (VCS) is transported in *Cylinders* (artifacts) via
398
  *Jetstream* (fleet transport) driven by *Flow* (pipelines).
399
- **Where it lives:** Initially in `oxygenflow` / `crates/oxygen-cylinder`
400
  alongside `jet` and `flow` to share `flow-casfs` and chunking primitives,
401
  splitting into an independent repository at C3.
402
- **Local builds:** Allowed, but marked with lower assurance level
403
  (`local`). Production channel rules require higher assurance levels
404
  (`tier-microvm` + `reproduced(2)`).
405
- **A public catalogue / Federation:** Public hubs federate by publishing
406
  signed Strata bundles of channels and artifacts, verified offline
407
  without centralized intermediaries.
408
- **Place in the queue:** C0/C1 should proceed alongside Jetstream's
409
  service boundary work, ensuring Jetstream's image store is built
410
  directly on Cylinder rather than creating a temporary mechanism.
411
412
## Sources
413
414
- 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
415
- GHCR billing: [GitHub community discussion #183054]https://github.com/orgs/community/discussions/183054
416
- GitLab cleanup policies and garbage collection: [GitLab docs, reduce container registry storage]https://docs.gitlab.com/user/packages/container_registry/reduce_container_registry_storage/
417
- 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
418
- 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
419
- zstd:chunked: [Red Hat, faster container image pulls]https://www.redhat.com/sysadmin/faster-container-image-pulls
420
- 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/
421
- 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
422
- 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
423
- 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