///|
/// Process newline-delimited JSON (NDJSON) in one incremental pass.
///
/// The first non-blank line is a session header:
/// `{"schema_version":1,"config":{...}}`. Every later non-blank line is one
/// command object, exactly as it would appear in a replay `commands` array.
/// The result is one outcome line per command followed by a final
/// `{"summary":{...}}` line, so the engine can be driven by any line-oriented
/// producer without writing MoonBit or re-parsing a whole batch.
pub fn stream_json(source : String) -> String raise TideError {
  if source.length() > 8388608 {
    fail("JSON exceeds 8 Mi UTF-16 units")
  }
  let lines : Array[String] = []
  for line in source.split("\n") {
    let trimmed = line.trim().to_owned()
    if trimmed.length() > 0 {
      lines.push(trimmed)
    }
  }
  if lines.length() < 1 {
    fail("stream requires a config header line")
  }
  let head = obj(strict_json(lines[0]))
  fields_only(head, ["schema_version", "config"])
  if head.get("schema_version") is Some(v) {
    if integer(v) != 1 {
      fail("unsupported schema_version")
    }
  }
  let c = read_config(req(head, "config"))
  if lines.length() - 1 > 10000 {
    fail("at most 10000 commands per stream")
  }
  let engine = Engine::new(c)
  let out : Array[String] = []
  for i in 1..