OXYGENHUB

flow check points at \u{1b} for ANSI colours b647ab72d51e

KDL 2.0 strings have no \033, \e or \x escapes; when a pipeline fails
to parse and holds one, flow check adds a problem on its line saying to
write ESC as \u{1b}, which passes through to the job (checked in a
microVM on Earth). The spec says so, and that /dev/fd works in the guest.

closes #17
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-30 14:02 UTC
Signature
✔ signed by Andy McCall
Hash
b647ab72d51eb28e49f7a940ebb677d88bffe6789f143259c51b2877a0249786
Depends on
a083cc802892 a67b97e9a717 e0918b887850
Depended on by
e576e08617dc
In bookmarks
main newer-pipeline push-uses-login
In tags
v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.5.0 v0.5.1 v0.5.2 v0.5.3
Edits
4 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified crates/flow-core/src/pipeline.rs +21 −0
…
306 unchanged lines
307 307
    Some(mib)
308 308
}
309 309
310 +
/// The line of the first escape KDL 2.0 does not have but shells and C
311 +
/// do (`\0NN`, `\e`, `\xNN`), as someone colouring output writes them.
312 +
fn foreign_escape_line(src: &str) -> Option<usize> {
313 +
    src.lines()
314 +
        .position(|l| {
315 +
            let b = l.as_bytes();
316 +
            b.windows(2).enumerate().any(|(i, w)| {
317 +
                w[0] == b'\\'
318 +
                    && (i == 0 || b[i - 1] != b'\\')
319 +
                    && (w[1] == b'e' || w[1] == b'x' || (w[1] == b'0' && b.get(i + 2).is_some_and(u8::is_ascii_digit)))
320 +
            })
321 +
        })
322 +
        .map(|i| i + 1)
323 +
}
324 +
310 325
impl Pipeline {
311 326
    /// Parse and validate a pipeline document.
312 327
    pub fn parse(src: &str) -> Result<Pipeline, Problems> {
…
7 unchanged lines
320 335
                }
321 336
                if ctx.problems.is_empty() {
322 337
                    ctx.problems.push(Problem { line: 1, message: "not valid KDL 2.0".into() });
338 +
                }
339 +
                if let Some(line) = foreign_escape_line(src) {
340 +
                    ctx.problems.push(Problem {
341 +
                        line,
342 +
                        message: "KDL strings have no \\033, \\e or \\x escapes: write ESC (for ANSI colours) as \\u{1b}".into(),
343 +
                    });
323 344
                }
324 345
                return Err(Problems(ctx.problems));
325 346
            }
…
495 unchanged lines
modified crates/flow-core/src/tests.rs +10 −0
…
357 unchanged lines
358 358
    assert!(bad("    output {\n        artifact \"x\" {\n            path \"/src/a\"\n            format \"tar\"\n        }\n    }\n")
359 359
        .contains("format"));
360 360
}
361 +
362 +
/// metalgear feedback 19 (oxygenflow#17): `\033` in a string is not KDL;
363 +
/// the problems say to write ESC as `\u{1b}`, which parses.
364 +
#[test]
365 +
fn ansi_escapes_are_written_as_unicode() {
366 +
    let bad = problems(&doc("job \"a\" {\n    sandbox \"alpine\"\n    exec \"printf\" \"\\033[32mok\\033[0m\"\n}\n"));
367 +
    assert!(bad.iter().any(|(line, m)| *line == 13 && m.contains("\\u{1b}")), "{bad:?}");
368 +
    let p = Pipeline::parse(&doc("job \"a\" {\n    sandbox \"alpine\"\n    exec \"printf\" \"\\u{1b}[32mok\\u{1b}[0m\"\n}\n")).unwrap();
369 +
    assert_eq!(p.jobs["a"].exec[1], "\u{1b}[32mok\u{1b}[0m");
370 +
}
modified docs/spec.md +6 −0
…
73 unchanged lines
74 74
repository root. KDL provides strict typing, structured nesting, and rejects
75 75
arbitrary text macro expansion.
76 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 +
77 83
### 3.1 Pipeline Specification Schema
78 84
79 85
```kdl
…
327 unchanged lines