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
b647ab72 Andy McCall 1 month ago ↶
77
Strings follow KDL 2.0, which has no ` \033` , ` \e` or ` \x` escapes: ESC,
78
for ANSI colours in a job's output, is ` \u{1b}` (` "\u{1b}[32mok\u{1b}[0m"` ),
79
and ` flow check` says so when it meets one of the others. Inside the
80
guest, ` /dev/fd` and ` /dev/stdin|stdout|stderr` link to ` /proc/self/fd`
81
as on any Linux system, so bash process substitution works.
82
a083cc80 Andy McCall 1 month ago ↶
83
### 3.1 Pipeline Specification Schema
84
85
``` kdl
86
schema-version 1
87
88
// Define the hermetic execution boundary
89
sandbox "rust-builder" {
90
runtime "microvm" // Options: "wasm" | "gvisor" | "microvm"
91
rootfs "cas://blob/9b5c2a1e8f3d4e7a6b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a"
92
memory-limit "4GiB"
93
cpu-limit 4
94
network "isolated" // Options: "isolated" | "loopback" | "egress"
95
}
96
97
// Job evaluated based on Oxygen change closure
98
job "compile-core" {
99
sandbox "rust-builder"
100
101
// Commutation boundary: runs only if change closure touches these paths
102
scope "crates/oxy-core/**" "crates/oxy-server/**" "Cargo.*"
103
104
// Zero-copy workspace mount backed by Oxygen CAS
105
mount {
106
source "oxy://workspace"
107
target "/src"
108
include "crates/oxy-core" "crates/oxy-server" "Cargo.toml" "Cargo.lock"
109
readonly #true
110
}
111
112
// Ephemeral scratch space
113
mount {
114
source "ephemeral://tmpfs"
115
target "/src/target"
116
size "8GiB"
117
}
118
119
env {
120
RUSTFLAGS "-Dwarnings"
121
CARGO_INCREMENTAL "0"
122
}
123
124
// Direct argv execution vector (no subshell)
125
exec "cargo" "build" "--release" "--package" "oxy-core"
126
127
output {
128
artifact "oxy-core-lib" {
129
path "/src/target/release/liboxy_core.rlib"
130
format "blob"
131
}
132
}
133
}
134
135
job "unit-tests" {
136
sandbox "rust-builder"
137
depends-on "compile-core"
138
139
mount {
140
source "oxy://workspace"
141
target "/src"
142
readonly #true
143
}
144
145
mount {
146
source "artifact://compile-core/oxy-core-lib"
147
target "/src/target/release/liboxy_core.rlib"
148
}
149
150
exec "cargo" "test" "--release" "--package" "oxy-core"
151
}
152
153
publish "release-provenance" {
154
depends-on "unit-tests"
155
156
attest {
157
kind "provenance"
158
bind-ref "bookmarks/main"
159
include-artifacts "compile-core/oxy-core-lib"
160
}
161
}
162
163
e576e086 Andy McCall 1 month ago ↶
164
**Runner labels.** A sandbox or a job may say `runs-on "LABEL" ...`: lower-case letters, digits, `-`, `_` and `.`, at most 64 bytes each, given on one or more lines. A job needs a runner that has every label its sandbox and it name. A runner reports its labels and architecture when it registers and on each claim (`flow runner register --label`). The hub hands a job only to a runner with all of them, within the runner's scope (OxygenHub spec 13.2), and a job no runner can take waits, saying so on the queue. Labels are part of the compiled job, so a job that asks for none compiles as before.
165
a083cc80 Andy McCall 1 month ago ↶
166
### 3.2 Canonical Compilation
167
Upon commit, .flow/pipeline.kdl is validated and compiled into a deterministic
168
CBOR job-graph blob. Runners evaluate the canonical CBOR to prevent syntax or
169
parser ambiguities across varying client platforms.
170
171
## 4. Sandbox Execution Engine
172
173
OxygenFlow implements three distinct isolation tiers depending on the workload:
174
175
### 4.1 Level 0: WASM Component Sandbox
176
- Intended for static linters, formatters, doc generators, and policy checks.
177
- Runs on a standard WebAssembly System Interface (WASI) runtime.
178
- Startup latency: < 5 ms.
179
- Zero host I/O; virtualized file system presented in-memory.
180
181
### 4.2 Level 1: Intercepted Syscall Isolation (gVisor / Landlock + seccomp-bpf)
182
- Intended for standard compilation and hermetic test suites.
183
- Emulates or filters Linux system call interfaces in user space.
184
- Blocks access to raw network sockets, real-time hardware clocks, and device
185
nodes.
186
- Memory and execution CPU quotas are strictly bounded via cgroups v2.
187
188
### 4.3 Level 2: MicroVM Isolation (Firecracker / Cloud-Hypervisor)
189
- Intended for kernel tests, multi-container integration, and untrusted code.
190
- Ephemeral Linux microVM booting in < 80 ms.
191
- The root filesystem is presented as a read-only virtio-block device mapped
192
directly to FastCDC chunks stored in Oxygen's CAS.
193
- Guest VM memory is cleared upon termination.
194
195
## 5. Content-Addressable Storage (CAS) & Zero-Copy Mounting
196
197
OxygenFlow runners bypass traditional clone operations. Repositories are accessed
198
directly via the local object store (.oxy/objects/) or streamed on demand
199
using Oxygen's wire protocol.
200
201
### 5.1 FUSE Overlay Mounting (flow-casfs)
202
1. The runner receives a task targeting an Oxygen State ObjectRef (a sorted
203
list of maximal heads).
204
2. The nearest Checkpoint is resolved. Uncommitted changes between the
205
checkpoint and heads are replayed into a virtual tree in-memory.
206
3. The virtual tree is exposed to the sandbox via flow-casfs:
207
- File reads resolve chunks using the FastCDC gear table (32 KiB to 256 KiB,
208
mean 64 KiB) pinned in Oxygen Spec Section 13.2.
209
- Missing chunks are fetched lazily using the POST /objects protocol.
210
- To prevent FUSE context-switch bottlenecks on many small reads, flow-casfs aggressively mmaps chunk data and utilizes host page caching.
211
- All guest mutations are diverted to a scratch tmpfs layer; base objects
212
remain immutable.
213
214
### 5.2 Artifact Capture
215
1. At task completion, flow-casfs scans the paths declared in the output block.
216
2. Modified outputs are chunked with FastCDC, hashed via BLAKE3-256, and
217
written as canonical Blob and Chunk objects.
218
3. Output ObjectRefs are stored directly in the Oxygen repository.
219
220
## 6. Commutative Execution & Change Caching
221
222
In Oxygen, changes that do not share scopes (referenced atoms, placements, or
223
nodes) commute. OxygenFlow exploits this invariant to provide granular caching.
224
225
### 6.1 Execution Receipt Object
226
Every successful or failed job run emits an immutable Receipt:
227
228
```cbor
229
Receipt {
230
version [0]: 1
231
job_name [1]: text
232
pipeline_hash [2]: ObjectRef
233
scope_tree_hash [3]: ObjectRef // BLAKE3 tree hash of the materialized scope
234
inputs [4]: [ObjectRef] // Artifacts and input files consumed
235
environment [5]: ObjectRef // Hash of rootfs, compiler flags, and argv
236
outputs [6]: [ObjectRef] // Produced artifact blobs
237
exit_code [7]: u32
238
duration_ms [8]: u64
239
}
240
```
241
242
A Receipt is hashed as an Oxygen object with type name "receipt":
243
ObjectRef = BLAKE3-256("oxy1 receipt " || decimal(len) || 0x00 || body)
244
245
### 6.2 Cache Re-use Invariant
246
Let Scope(J) be the file paths and tree nodes declared by job J.
247
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).
248
249
If an existing Receipt R exists such that:
250
R.job_name == J.name & &
251
R.pipeline_hash == Hash(J) & &
252
R.scope_tree_hash == TreeHash(Scope(J)) & &
253
R.environment == CurrentEnvironmentHash
254
255
Then the job execution is bypassed. R.outputs are mapped into downstream
256
dependents immediately as a cached-commuted hit.
257
258
## 7. Native Provenance Specification
259
260
OxygenFlow formalizes build provenance as a native, tamper-evident Oxygen Attest
261
object (kind: "attest"). Provenance is generated exclusively by the runner
262
hypervisor, not the guest payload.
263
264
### 7.1 Provenance Object Schema
265
266
``` json
267
Attest {
268
version [0]: 1
269
kind [1]: "attest"
270
repo [2]: bytes(16) // Oxygen Repository ID
271
hlc [3]: HLC // Hybrid Logical Clock
272
body [4]: {
273
"type": "flow/provenance/v1",
274
275
// Subject artifacts produced by the build
276
"subjects": [
277
{
278
"name": text,
279
"ref": ObjectRef, // BLAKE3 hash of output blob
280
"media_type": text
281
}
282
],
283
284
// Builder platform and execution identity
285
"builder": {
286
"runner_id": KeyId, // ObjectRef of runner's Oxygen Identity
287
"isolation_level": text, // "wasm" | "gvisor" | "microvm"
288
"hypervisor_image": ObjectRef // Rootfs blob digest
289
},
290
291
// Exact input materials
292
"materials": {
293
"state_heads": [ChangeHash], // Oxygen heads of the state built
294
"scope_tree_hash": ObjectRef, // Captures exact state without O(N) array bloat
295
"inputs": [
296
{
297
"name": text,
298
"ref": ObjectRef // Content hash of locked dependencies
299
}
300
]
301
},
302
303
// Structured execution arguments
304
"invocation": {
305
"job": text,
306
"pipeline_spec": ObjectRef, // ObjectRef of .flow/pipeline.kdl
307
"argv": [text], // Unmodified execution vector
308
"env": map<text, text> // Explicitly whitelisted environment
309
},
310
311
// Environment observations
312
"metadata": {
313
"started_on": u64, // Wallclock milliseconds
314
"finished_on": u64,
315
"hermetic": bool, // Verified zero outbound network access
316
"reproducible": bool
317
}
318
}
319
}
320
```
321
322
### 7.2 Composite Signatures
323
All provenance attestations are signed using Oxygen's composite signature
324
construction (Ed25519 and ML-DSA-65):
325
326
``` cbor
327
Signature {
328
key [0]: KeyId
329
alg [1]: 1 // 1 = Ed25519 + ML-DSA-65
330
ed [2]: bytes(64) // Classical signature
331
ml [3]: bytes(3309) // Post-quantum signature
332
}
333
```
334
335
Both signatures are computed over:
336
"oxy1 sig attest\0" || KeyId || ObjectRef
337
338
## 8. Policy Integration & Bookmark Gates
339
340
Oxygen repositories enforce branch and bookmark protections via signed Policy
341
objects (Oxygen Spec Section 14.5). OxygenFlow integrates provenance directly into
342
policy evaluation during push validation.
343
344
### 8.1 Policy Gate Definition
345
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.
346
347
``` json
348
PolicyRule {
349
pattern: "bookmarks/production"
350
keys: [ KeyId ]
351
threshold: 1
352
require: "signed"
353
allow_conflicts: false
354
require_attestations: [
355
{
356
type: "flow/provenance/v1",
357
job: "release-provenance",
358
require_hermetic: true,
359
signer_roles: [ "official-worker" ]
360
}
361
]
362
}
363
364
### 8.2 Push Verification Sequence (VCS Engine Enforcement)
365
When a push request arrives at an Oxygen server attempting to update a
366
protected bookmark:
367
1. Object Integrity: Validate that all changes, chunks, and blobs exist and match
368
their BLAKE3 hashes.
369
2. Attestation Resolution: Verify that an Attest object of type
370
"flow/provenance/v1" is present, referencing the exact heads of the new
371
State.
372
3. Identity Verification: Validate that the attestation's composite signature
373
resolves to a KeyId listed as an authorized delegate of the repository
374
policy roots.
375
4. Invariant Checking: Confirm that the provenance statement records
376
"hermetic: true" and exit code 0.
377
5. Atomic Update: If validation fails at any point, the entire push is refused
378
with ok = false.
379
380
## 9. Zero-Environment Secret Brokering
381
382
OxygenFlow forbids the injection of raw secret strings into process environment
383
tables to eliminate accidental leaking via child process dumping, procfs
384
reads, and verbose compiler logging.
385
386
### 9.1 Ephemeral Secret Broker
387
1. If a job requires a credential (e.g., signing key, cloud deployment token),
388
it declares a secret mount:
389
secret "deploy-token" {
390
vault-ref "infra/tokens/prod"
391
mount-path "/run/secrets/token"
392
}
393
2. The runner negotiates a short-lived token lease from the configured vault.
394
3. The secret is mounted onto an ephemeral, in-memory ramfs location inside
395
the sandbox with access mode 0400, owned exclusively by the worker process.
396
4. Upon process termination, the memory pages are zeroized and the token is
397
marked for immediate revocation at the vault.
398
399
## 10. Conformance and Verification Requirements
400
401
A conforming OxygenFlow implementation MUST pass the following tests:
402
403
1. Injection Immunity: Attempting to supply unescaped shell metacharacters
404
(; && | ` $() \n) in any job field MUST pass those characters as literal
405
bytes to argv; subshells MUST NOT be spawned.
406
2. FastCDC Determinism: Chunks produced by artifact extraction MUST match
407
the gear table cuts and BLAKE3 hashes generated by Oxygen's core chunker.
408
3. Commutation Hit Invariance: For any two commuting changes A and B, running
409
a pipeline on State(A, B) MUST reuse cached receipts generated on State(A)
410
for any job whose scope does not intersect the touched paths of B.
411
4. Non-Downgrade Attestation: An attestation missing the ML-DSA-65 component
412
MUST be rejected by policy verification.
413
5. Hermetic Network Isolation: Jobs configured with network "isolated" MUST
414
have outgoing TCP SYN and UDP packets dropped at the hypervisor boundary.