OXYGENHUB
# 720eb8191059 oxygenflow / docs / spec.md
16.8 KiB · Markdown · last changed pipeline.kdl: timeout on a job or a sandbox 1 month ago renderedsource blame history raw
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
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
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
164
**Time limits.** A job may say how long it may run with `timeout "90m"` (hours, minutes and seconds, as `"2h"`, `"1h30m"` or `"45s"`, from a minute to a day); a sandbox's `timeout` is the default for its jobs. Without one, the hub's default applies, and the hub caps every job at the site's maximum (both set by an administrator on Admin → Runners). The runner stops a job at its limit, and the hub ends one its runner stopped reporting a few minutes after it. A timeout is part of the compiled job, so a job without one compiles as before.
165
166
**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.
167
168
### 3.2 Canonical Compilation
169
Upon commit, .flow/pipeline.kdl is validated and compiled into a deterministic
170
CBOR job-graph blob. Runners evaluate the canonical CBOR to prevent syntax or
171
parser ambiguities across varying client platforms.
172
173
## 4. Sandbox Execution Engine
174
175
OxygenFlow implements three distinct isolation tiers depending on the workload:
176
177
### 4.1 Level 0: WASM Component Sandbox
178
- Intended for static linters, formatters, doc generators, and policy checks.
179
- Runs on a standard WebAssembly System Interface (WASI) runtime.
180
- Startup latency: < 5 ms.
181
- Zero host I/O; virtualized file system presented in-memory.
182
183
### 4.2 Level 1: Intercepted Syscall Isolation (gVisor / Landlock + seccomp-bpf)
184
- Intended for standard compilation and hermetic test suites.
185
- Emulates or filters Linux system call interfaces in user space.
186
- Blocks access to raw network sockets, real-time hardware clocks, and device
187
  nodes.
188
- Memory and execution CPU quotas are strictly bounded via cgroups v2.
189
190
### 4.3 Level 2: MicroVM Isolation (Firecracker / Cloud-Hypervisor)
191
- Intended for kernel tests, multi-container integration, and untrusted code.
192
- Ephemeral Linux microVM booting in < 80 ms.
193
- The root filesystem is presented as a read-only virtio-block device mapped
194
  directly to FastCDC chunks stored in Oxygen's CAS.
195
- Guest VM memory is cleared upon termination.
196
197
## 5. Content-Addressable Storage (CAS) & Zero-Copy Mounting
198
199
OxygenFlow runners bypass traditional clone operations. Repositories are accessed
200
directly via the local object store (.oxy/objects/) or streamed on demand
201
using Oxygen's wire protocol.
202
203
### 5.1 FUSE Overlay Mounting (flow-casfs)
204
1. The runner receives a task targeting an Oxygen State ObjectRef (a sorted
205
   list of maximal heads).
206
2. The nearest Checkpoint is resolved. Uncommitted changes between the
207
   checkpoint and heads are replayed into a virtual tree in-memory.
208
3. The virtual tree is exposed to the sandbox via flow-casfs:
209
   - File reads resolve chunks using the FastCDC gear table (32 KiB to 256 KiB,
210
     mean 64 KiB) pinned in Oxygen Spec Section 13.2.
211
   - Missing chunks are fetched lazily using the POST /objects protocol.
212
   - To prevent FUSE context-switch bottlenecks on many small reads, flow-casfs aggressively mmaps chunk data and utilizes host page caching.
213
   - All guest mutations are diverted to a scratch tmpfs layer; base objects
214
     remain immutable.
215
216
### 5.2 Artifact Capture
217
1. At task completion, flow-casfs scans the paths declared in the output block.
218
2. Modified outputs are chunked with FastCDC, hashed via BLAKE3-256, and
219
   written as canonical Blob and Chunk objects.
220
3. Output ObjectRefs are stored directly in the Oxygen repository.
221
222
## 6. Commutative Execution & Change Caching
223
224
In Oxygen, changes that do not share scopes (referenced atoms, placements, or
225
nodes) commute. OxygenFlow exploits this invariant to provide granular caching.
226
227
### 6.1 Execution Receipt Object
228
Every successful or failed job run emits an immutable Receipt:
229
230
```cbor
231
Receipt {
232
  version        [0]: 1
233
  job_name       [1]: text
234
  pipeline_hash  [2]: ObjectRef
235
  scope_tree_hash [3]: ObjectRef     // BLAKE3 tree hash of the materialized scope
236
  inputs         [4]: [ObjectRef]   // Artifacts and input files consumed
237
  environment    [5]: ObjectRef     // Hash of rootfs, compiler flags, and argv
238
  outputs        [6]: [ObjectRef]   // Produced artifact blobs
239
  exit_code      [7]: u32
240
  duration_ms    [8]: u64
241
}
242
```
243
244
A Receipt is hashed as an Oxygen object with type name "receipt":
245
ObjectRef = BLAKE3-256("oxy1 receipt " || decimal(len) || 0x00 || body)
246
247
### 6.2 Cache Re-use Invariant
248
Let Scope(J) be the file paths and tree nodes declared by job J.
249
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).
250
251
If an existing Receipt R exists such that:
252
  R.job_name == J.name &&
253
  R.pipeline_hash == Hash(J) &&
254
  R.scope_tree_hash == TreeHash(Scope(J)) &&
255
  R.environment == CurrentEnvironmentHash
256
257
Then the job execution is bypassed. R.outputs are mapped into downstream
258
dependents immediately as a cached-commuted hit.
259
260
## 7. Native Provenance Specification
261
262
OxygenFlow formalizes build provenance as a native, tamper-evident Oxygen Attest
263
object (kind: "attest"). Provenance is generated exclusively by the runner
264
hypervisor, not the guest payload.
265
266
### 7.1 Provenance Object Schema
267
268
```json
269
Attest {
270
  version [0]: 1
271
  kind    [1]: "attest"
272
  repo    [2]: bytes(16)               // Oxygen Repository ID
273
  hlc     [3]: HLC                     // Hybrid Logical Clock
274
  body    [4]: {
275
    "type": "flow/provenance/v1",
276
    
277
    // Subject artifacts produced by the build
278
    "subjects": [
279
      {
280
        "name": text,
281
        "ref": ObjectRef,              // BLAKE3 hash of output blob
282
        "media_type": text
283
      }
284
    ],
285
286
    // Builder platform and execution identity
287
    "builder": {
288
      "runner_id": KeyId,              // ObjectRef of runner's Oxygen Identity
289
      "isolation_level": text,         // "wasm" | "gvisor" | "microvm"
290
      "hypervisor_image": ObjectRef    // Rootfs blob digest
291
    },
292
293
    // Exact input materials
294
    "materials": {
295
      "state_heads": [ChangeHash],     // Oxygen heads of the state built
296
      "scope_tree_hash": ObjectRef,        // Captures exact state without O(N) array bloat
297
      "inputs": [
298
        {
299
          "name": text,
300
          "ref": ObjectRef             // Content hash of locked dependencies
301
        }
302
      ]
303
    },
304
305
    // Structured execution arguments
306
    "invocation": {
307
      "job": text,
308
      "pipeline_spec": ObjectRef,      // ObjectRef of .flow/pipeline.kdl
309
      "argv": [text],                  // Unmodified execution vector
310
      "env": map<text, text>           // Explicitly whitelisted environment
311
    },
312
313
    // Environment observations
314
    "metadata": {
315
      "started_on": u64,               // Wallclock milliseconds
316
      "finished_on": u64,
317
      "hermetic": bool,                // Verified zero outbound network access
318
      "reproducible": bool
319
    }
320
  }
321
}
322
```
323
324
### 7.2 Composite Signatures
325
All provenance attestations are signed using Oxygen's composite signature
326
construction (Ed25519 and ML-DSA-65):
327
328
```cbor
329
Signature {
330
  key [0]: KeyId
331
  alg [1]: 1                           // 1 = Ed25519 + ML-DSA-65
332
  ed  [2]: bytes(64)                   // Classical signature
333
  ml  [3]: bytes(3309)                 // Post-quantum signature
334
}
335
```
336
337
Both signatures are computed over:
338
  "oxy1 sig attest\0" || KeyId || ObjectRef
339
340
## 8. Policy Integration & Bookmark Gates
341
342
Oxygen repositories enforce branch and bookmark protections via signed Policy
343
objects (Oxygen Spec Section 14.5). OxygenFlow integrates provenance directly into
344
policy evaluation during push validation.
345
346
### 8.1 Policy Gate Definition
347
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.
348
349
```json
350
PolicyRule {
351
  pattern: "bookmarks/production"
352
  keys: [ KeyId ]
353
  threshold: 1
354
  require: "signed"
355
  allow_conflicts: false
356
  require_attestations: [
357
    {
358
      type: "flow/provenance/v1",
359
      job: "release-provenance",
360
      require_hermetic: true,
361
      signer_roles: [ "official-worker" ]
362
    }
363
  ]
364
}
365
366
### 8.2 Push Verification Sequence (VCS Engine Enforcement)
367
When a push request arrives at an Oxygen server attempting to update a
368
protected bookmark:
369
1. Object Integrity: Validate that all changes, chunks, and blobs exist and match
370
   their BLAKE3 hashes.
371
2. Attestation Resolution: Verify that an Attest object of type
372
   "flow/provenance/v1" is present, referencing the exact heads of the new
373
   State.
374
3. Identity Verification: Validate that the attestation's composite signature
375
   resolves to a KeyId listed as an authorized delegate of the repository
376
   policy roots.
377
4. Invariant Checking: Confirm that the provenance statement records
378
   "hermetic: true" and exit code 0.
379
5. Atomic Update: If validation fails at any point, the entire push is refused
380
   with ok = false.
381
382
## 9. Zero-Environment Secret Brokering
383
384
OxygenFlow forbids the injection of raw secret strings into process environment
385
tables to eliminate accidental leaking via child process dumping, procfs
386
reads, and verbose compiler logging.
387
388
### 9.1 Ephemeral Secret Broker
389
1. If a job requires a credential (e.g., signing key, cloud deployment token),
390
   it declares a secret mount:
391
     secret "deploy-token" {
392
         vault-ref "infra/tokens/prod"
393
         mount-path "/run/secrets/token"
394
     }
395
2. The runner negotiates a short-lived token lease from the configured vault.
396
3. The secret is mounted onto an ephemeral, in-memory ramfs location inside
397
   the sandbox with access mode 0400, owned exclusively by the worker process.
398
4. Upon process termination, the memory pages are zeroized and the token is
399
   marked for immediate revocation at the vault.
400
401
## 10. Conformance and Verification Requirements
402
403
A conforming OxygenFlow implementation MUST pass the following tests:
404
405
1. Injection Immunity: Attempting to supply unescaped shell metacharacters
406
   (; && | ` $() \n) in any job field MUST pass those characters as literal
407
   bytes to argv; subshells MUST NOT be spawned.
408
2. FastCDC Determinism: Chunks produced by artifact extraction MUST match
409
   the gear table cuts and BLAKE3 hashes generated by Oxygen's core chunker.
410
3. Commutation Hit Invariance: For any two commuting changes A and B, running
411
   a pipeline on State(A, B) MUST reuse cached receipts generated on State(A)
412
   for any job whose scope does not intersect the touched paths of B.
413
4. Non-Downgrade Attestation: An attestation missing the ML-DSA-65 component
414
   MUST be rejected by policy verification.
415
5. Hermetic Network Isolation: Jobs configured with network "isolated" MUST
416
   have outgoing TCP SYN and UDP packets dropped at the hypervisor boundary.