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 1 month ago · 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
crates/flow-core/src/pipeline.rs crates/flow-core/src/tests.rs docs/spec.md
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