a083cc80 Andy McCall 1 month ago ↶
1
# OxygenFlow (flow) — Deterministic Execution & Provenance Pipeline Specification
2
3
4
5
| ** Format version** | 1 |
6
| ** Date** | 2026-09-28 |
7
| ** Status** | Draft 0.1 |
8
| ** Target VCS** | Oxygen (oxy) Draft 0.8i |
9
| ** Audience** | Implementers, Security Architects, and Systems Engineers |
10
11
12
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be
13
interpreted as described in RFC 2119.
14
15
## 1. Overview and Core Invariants
16
17
OxygenFlow (flow) is a content-addressed, hermetic execution engine, runner system,
18
and pipeline language designed natively for the Oxygen (oxy) version control
19
system.
20
21
Existing runner engines (GitHub Actions, GitLab CI, Forgejo Runner) suffer from
22
structural flaws:
23
1. Workspace Bloat: Full clones, index unpacks, and checkout scans dominate I/O.
24
2. String Injection: Shell interpolation within untyped YAML files produces
25
expression injection and command execution exploits.
26
3. Decoupled Supply Chain: Attestations (SLSA, in-toto, Cosign) exist outside
27
the VCS, relying on external registries rather than native repository trust.
28
4. Coarse Caching: Opaque tarballs keyed by arbitrary string hashes fail to
29
leverage commutative history or granular dependency graphs.
30
31
OxygenFlow solves these liabilities through direct integration with Oxygen:
32
- Zero-Copy Workspaces: Jobs mount workspaces directly from Oxygen's FastCDC-
33
chunked, BLAKE3-addressed Content-Addressable Store (CAS) using Copy-on-Write
34
(COW) overlays.
35
- Commutative History & Impact Trees: Pipelines evaluate over change closures
36
rather than linear snapshot sequences. Commuting changes automatically reuse
37
cached execution receipts without redundant re-execution.
38
- Micro-Engine Sandboxing: Jobs run inside isolated environments (WASM, gVisor,
39
or MicroVMs) with strict, deny-by-default capabilities and no ambient
40
network egress.
41
- Native Cryptographic Provenance: Provenance and execution receipts are
42
generated outside the untrusted guest, encoded as deterministic CBOR, and
43
signed using composite Ed25519 + ML-DSA-65 keys as native Oxygen Attest
44
objects governed by in-repository Policy rules.
45
46
## 2. Threat Model and Security Architecture
47
48
### 2.1 Adversaries
49
- Untrusted Proposal Author: Submits code to an open repository containing
50
malicious build configurations intended to escape the runner, exfiltrate
51
secrets, or corrupt shared worker caches.
52
- Malicious Dependency / Upstream: Third-party dependencies invoked during
53
build time attempting out-of-band network calls or host file modification.
54
- Compromised Runner / Malicious Worker: An execution node attempting to
55
forge successful build receipts or inject backdoors into published artifacts.
56
57
### 2.2 Security Invariants
58
- I1 Deny-by-Default Execution: Sandboxes possess no network interfaces
59
(excluding loopback when requested) and no host filesystem access unless
60
explicitly granted in policy-signed configuration.
61
- I2 Injection-Free Invocation: Configuration parsing directly yields an argv
62
string vector executed via execve(). Shell interpretation (/bin/sh -c) does
63
not exist.
64
- I3 Host-Enforced Provenance: The running payload cannot author, inspect, or
65
alter its own provenance statement. Provenance is emitted solely by the
66
isolation hypervisor boundary.
67
- I4 Cryptographic Identity & Authorization: Runners execute using an Oxygen
68
Identity whose KeyId is enrolled as a delegate in the repository's Policy.
69
Unenrolled runners cannot publish recognized attestations or advance gates.
70
71
## 3. The OxygenFlow Configuration Language (.flow)
72
73
Pipelines are declared in KDL syntax within .flow/pipeline.kdl at the
74
repository root. KDL provides strict typing, structured nesting, and rejects
75
arbitrary text macro expansion.
76
77
### 3.1 Pipeline Specification Schema
78
79
``` kdl
80
schema-version 1
81
82
// Define the hermetic execution boundary
83
sandbox "rust-builder" {
84
runtime "microvm" // Options: "wasm" | "gvisor" | "microvm"
85
rootfs "cas://blob/9b5c2a1e8f3d4e7a6b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a"
86
memory-limit "4GiB"
87
cpu-limit 4
88
network "isolated" // Options: "isolated" | "loopback" | "egress"
89
}
90
91
// Job evaluated based on Oxygen change closure
92
job "compile-core" {
93
sandbox "rust-builder"
94
95
// Commutation boundary: runs only if change closure touches these paths
96
scope "crates/oxy-core/**" "crates/oxy-server/**" "Cargo.*"
97
98
// Zero-copy workspace mount backed by Oxygen CAS
99
mount {
100
source "oxy://workspace"
101
target "/src"
102
include "crates/oxy-core" "crates/oxy-server" "Cargo.toml" "Cargo.lock"
103
readonly #true
104
}
105
106
// Ephemeral scratch space
107
mount {
108
source "ephemeral://tmpfs"
109
target "/src/target"
110
size "8GiB"
111
}
112
113
env {
114
RUSTFLAGS "-Dwarnings"
115
CARGO_INCREMENTAL "0"
116
}
117
118
// Direct argv execution vector (no subshell)
119
exec "cargo" "build" "--release" "--package" "oxy-core"
120
121
output {
122
artifact "oxy-core-lib" {
123
path "/src/target/release/liboxy_core.rlib"
124
format "blob"
125
}
126
}
127
}
128
129
job "unit-tests" {
130
sandbox "rust-builder"
131
depends-on "compile-core"
132
133
mount {
134
source "oxy://workspace"
135
target "/src"
136
readonly #true
137
}
138
139
mount {
140
source "artifact://compile-core/oxy-core-lib"
141
target "/src/target/release/liboxy_core.rlib"
142
}
143
144
exec "cargo" "test" "--release" "--package" "oxy-core"
145
}
146
147
publish "release-provenance" {
148
depends-on "unit-tests"
149
150
attest {
151
kind "provenance"
152
bind-ref "bookmarks/main"
153
include-artifacts "compile-core/oxy-core-lib"
154
}
155
}
156
157
158
### 3.2 Canonical Compilation
159
Upon commit, .flow/pipeline.kdl is validated and compiled into a deterministic
160
CBOR job-graph blob. Runners evaluate the canonical CBOR to prevent syntax or
161
parser ambiguities across varying client platforms.
162
163
## 4. Sandbox Execution Engine
164
165
OxygenFlow implements three distinct isolation tiers depending on the workload:
166
167
### 4.1 Level 0: WASM Component Sandbox
168
- Intended for static linters, formatters, doc generators, and policy checks.
169
- Runs on a standard WebAssembly System Interface (WASI) runtime.
170
- Startup latency: < 5 ms.
171
- Zero host I/O; virtualized file system presented in-memory.
172
173
### 4.2 Level 1: Intercepted Syscall Isolation (gVisor / Landlock + seccomp-bpf)
174
- Intended for standard compilation and hermetic test suites.
175
- Emulates or filters Linux system call interfaces in user space.
176
- Blocks access to raw network sockets, real-time hardware clocks, and device
177
nodes.
178
- Memory and execution CPU quotas are strictly bounded via cgroups v2.
179
180
### 4.3 Level 2: MicroVM Isolation (Firecracker / Cloud-Hypervisor)
181
- Intended for kernel tests, multi-container integration, and untrusted code.
182
- Ephemeral Linux microVM booting in < 80 ms.
183
- The root filesystem is presented as a read-only virtio-block device mapped
184
directly to FastCDC chunks stored in Oxygen's CAS.
185
- Guest VM memory is cleared upon termination.
186
187
## 5. Content-Addressable Storage (CAS) & Zero-Copy Mounting
188
189
OxygenFlow runners bypass traditional clone operations. Repositories are accessed
190
directly via the local object store (.oxy/objects/) or streamed on demand
191
using Oxygen's wire protocol.
192
193
### 5.1 FUSE Overlay Mounting (flow-casfs)
194
1. The runner receives a task targeting an Oxygen State ObjectRef (a sorted
195
list of maximal heads).
701dcbc5 Andy McCall 1 month ago ↶
196
2. The state's tree is read from a state index (Oxygen's flat, checksummed
197
description of a history-only state: its changes, tree view and each
198
file's deferred history with its graph key), built once from the
199
nearest checkpoint plus the changes after it and kept beside the store.
200
Listing a directory needs nothing else.
201
3. The tree is exposed to the sandbox read-only through flow-casfs (FUSE):
202
- An opaque file's bytes are its stored blob, whose chunks (FastCDC,
203
32 KiB to 256 KiB, mean 64 KiB, Oxygen spec Section 13.2) are read as
204
they are needed.
205
- A text file's bytes are a rendering of its history, not a stored blob.
206
Renderings are cached by **graph key** (equal keys mean equal
207
renderings) as ordinary Blob and Chunk objects: a file is rendered on
208
first read or stat, and every later state in which it has the same
209
history finds it at once. A file added once and never edited is its
210
own text blob and needs no rendering at all.
211
- Missing objects are fetched lazily using the POST /objects protocol.
212
- To prevent FUSE context-switch bottlenecks on many small reads,
213
flow-casfs caches whole small files and mmaps chunk data, relying on
214
the host page cache.
215
- All guest mutations are diverted to an overlay on the job's ephemeral
216
disk (or a tmpfs); base objects remain immutable.
217
4. The same mount backs web workspaces (OxygenHub spec 13.3): a
218
`workspace` job keeps it for as long as its editor is open.
219
5. Targets, measured on the Linux tip: mount under 100 ms, a first read of
220
a file in milliseconds, and a full read of the tree no slower than a
221
checkout writing it.
a083cc80 Andy McCall 1 month ago ↶
222
223
### 5.2 Artifact Capture
224
1. At task completion, flow-casfs scans the paths declared in the output block.
225
2. Modified outputs are chunked with FastCDC, hashed via BLAKE3-256, and
226
written as canonical Blob and Chunk objects.
227
3. Output ObjectRefs are stored directly in the Oxygen repository.
228
229
## 6. Commutative Execution & Change Caching
230
231
In Oxygen, changes that do not share scopes (referenced atoms, placements, or
232
nodes) commute. OxygenFlow exploits this invariant to provide granular caching.
233
234
### 6.1 Execution Receipt Object
235
Every successful or failed job run emits an immutable Receipt:
236
237
```cbor
238
Receipt {
239
version [0]: 1
240
job_name [1]: text
241
pipeline_hash [2]: ObjectRef
242
scope_tree_hash [3]: ObjectRef // BLAKE3 tree hash of the materialized scope
243
inputs [4]: [ObjectRef] // Artifacts and input files consumed
244
environment [5]: ObjectRef // Hash of rootfs, compiler flags, and argv
245
outputs [6]: [ObjectRef] // Produced artifact blobs
246
exit_code [7]: u32
247
duration_ms [8]: u64
248
}
249
```
250
251
A Receipt is hashed as an Oxygen object with type name "receipt":
252
ObjectRef = BLAKE3-256("oxy1 receipt " || decimal(len) || 0x00 || body)
253
254
### 6.2 Cache Re-use Invariant
255
Let Scope(J) be the file paths and tree nodes declared by job J.
256
Because changes that do not share scopes commute, we do not need to track lists of ChangeHashes. Instead, we hash the materialized tree of Scope(J).
257
258
If an existing Receipt R exists such that:
259
R.job_name == J.name & &
260
R.pipeline_hash == Hash(J) & &
261
R.scope_tree_hash == TreeHash(Scope(J)) & &
262
R.environment == CurrentEnvironmentHash
263
264
Then the job execution is bypassed. R.outputs are mapped into downstream
265
dependents immediately as a cached-commuted hit.
266
267
## 7. Native Provenance Specification
268
269
OxygenFlow formalizes build provenance as a native, tamper-evident Oxygen Attest
270
object (kind: "attest"). Provenance is generated exclusively by the runner
271
hypervisor, not the guest payload.
272
273
### 7.1 Provenance Object Schema
274
275
``` json
276
Attest {
277
version [0]: 1
278
kind [1]: "attest"
279
repo [2]: bytes(16) // Oxygen Repository ID
280
hlc [3]: HLC // Hybrid Logical Clock
281
body [4]: {
282
"type": "flow/provenance/v1",
283
284
// Subject artifacts produced by the build
285
"subjects": [
286
{
287
"name": text,
288
"ref": ObjectRef, // BLAKE3 hash of output blob
289
"media_type": text
290
}
291
],
292
293
// Builder platform and execution identity
294
"builder": {
295
"runner_id": KeyId, // ObjectRef of runner's Oxygen Identity
296
"isolation_level": text, // "wasm" | "gvisor" | "microvm"
297
"hypervisor_image": ObjectRef // Rootfs blob digest
298
},
299
300
// Exact input materials
301
"materials": {
302
"state_heads": [ChangeHash], // Oxygen heads of the state built
303
"scope_tree_hash": ObjectRef, // Captures exact state without O(N) array bloat
304
"inputs": [
305
{
306
"name": text,
307
"ref": ObjectRef // Content hash of locked dependencies
308
}
309
]
310
},
311
312
// Structured execution arguments
313
"invocation": {
314
"job": text,
315
"pipeline_spec": ObjectRef, // ObjectRef of .flow/pipeline.kdl
316
"argv": [text], // Unmodified execution vector
317
"env": map<text, text> // Explicitly whitelisted environment
318
},
319
320
// Environment observations
321
"metadata": {
322
"started_on": u64, // Wallclock milliseconds
323
"finished_on": u64,
324
"hermetic": bool, // Verified zero outbound network access
325
"reproducible": bool
326
}
327
}
328
}
329
```
330
331
### 7.2 Composite Signatures
332
All provenance attestations are signed using Oxygen's composite signature
333
construction (Ed25519 and ML-DSA-65):
334
335
``` cbor
336
Signature {
337
key [0]: KeyId
338
alg [1]: 1 // 1 = Ed25519 + ML-DSA-65
339
ed [2]: bytes(64) // Classical signature
340
ml [3]: bytes(3309) // Post-quantum signature
341
}
342
```
343
344
Both signatures are computed over:
345
"oxy1 sig attest\0" || KeyId || ObjectRef
346
347
## 8. Policy Integration & Bookmark Gates
348
349
Oxygen repositories enforce branch and bookmark protections via signed Policy
350
objects (Oxygen Spec Section 14.5). OxygenFlow integrates provenance directly into
351
policy evaluation during push validation.
352
353
### 8.1 Policy Gate Definition
354
A Policy rule governing a bookmark MAY demand valid OxygenFlow attestations. To mitigate a compromised authorized worker, high-security repositories can require ** Consensus Builds** by setting the threshold > 1. This requires multiple independent runner identities to arrive at the exact same output hashes and both sign it.
355
356
``` json
357
PolicyRule {
358
pattern: "bookmarks/production"
359
keys: [ KeyId ]
360
threshold: 1
361
require: "signed"
362
allow_conflicts: false
363
require_attestations: [
364
{
365
type: "flow/provenance/v1",
366
job: "release-provenance",
367
require_hermetic: true,
368
signer_roles: [ "official-worker" ]
369
}
370
]
371
}
372
373
### 8.2 Push Verification Sequence (VCS Engine Enforcement)
374
When a push request arrives at an Oxygen server attempting to update a
375
protected bookmark:
376
1. Object Integrity: Validate that all changes, chunks, and blobs exist and match
377
their BLAKE3 hashes.
378
2. Attestation Resolution: Verify that an Attest object of type
379
"flow/provenance/v1" is present, referencing the exact heads of the new
380
State.
381
3. Identity Verification: Validate that the attestation's composite signature
382
resolves to a KeyId listed as an authorized delegate of the repository
383
policy roots.
384
4. Invariant Checking: Confirm that the provenance statement records
385
"hermetic: true" and exit code 0.
386
5. Atomic Update: If validation fails at any point, the entire push is refused
387
with ok = false.
388
389
## 9. Zero-Environment Secret Brokering
390
391
OxygenFlow forbids the injection of raw secret strings into process environment
392
tables to eliminate accidental leaking via child process dumping, procfs
393
reads, and verbose compiler logging.
394
395
### 9.1 Ephemeral Secret Broker
396
1. If a job requires a credential (e.g., signing key, cloud deployment token),
397
it declares a secret mount:
398
secret "deploy-token" {
399
vault-ref "infra/tokens/prod"
400
mount-path "/run/secrets/token"
401
}
402
2. The runner negotiates a short-lived token lease from the configured vault.
403
3. The secret is mounted onto an ephemeral, in-memory ramfs location inside
404
the sandbox with access mode 0400, owned exclusively by the worker process.
405
4. Upon process termination, the memory pages are zeroized and the token is
406
marked for immediate revocation at the vault.
407
408
## 10. Conformance and Verification Requirements
409
410
A conforming OxygenFlow implementation MUST pass the following tests:
411
412
1. Injection Immunity: Attempting to supply unescaped shell metacharacters
413
(; && | ` $() \n) in any job field MUST pass those characters as literal
414
bytes to argv; subshells MUST NOT be spawned.
415
2. FastCDC Determinism: Chunks produced by artifact extraction MUST match
416
the gear table cuts and BLAKE3 hashes generated by Oxygen's core chunker.
417
3. Commutation Hit Invariance: For any two commuting changes A and B, running
418
a pipeline on State(A, B) MUST reuse cached receipts generated on State(A)
419
for any job whose scope does not intersect the touched paths of B.
420
4. Non-Downgrade Attestation: An attestation missing the ML-DSA-65 component
421
MUST be rejected by policy verification.
422
5. Hermetic Network Isolation: Jobs configured with network "isolated" MUST
423
have outgoing TCP SYN and UDP packets dropped at the hypervisor boundary.