#1127·openui

createStreamParser() and parse() disagree on redefined statement IDs

Author: seshuthotaCreated Sep 6, 2026Updated Sep 9, 2026

parse() and createStreamParser() produce different final results for the same completed OpenUI Lang program when a statement ID is defined more than once.

The batch parser behaves as last definition wins, while the streaming parser behaves as first definition wins.

Minimal reproduction

const text = root = Stack([a]) a = Title("x") a = Title("y") ;

const batch = parse(text, schema); // root -> Title("y")

const stream = createStreamParser(schema); stream.push(text); const streamed = stream.getResult(); // root -> Title("x")

Actual behavior

For the same completed input:

parse(text) -> Title("y") createStreamParser().push() -> Title("x")

I also tested multiple delivery schedules:

the whole input in a single push()

line-by-line

character-by-character

multiple two-chunk splits

The streaming parser consistently returns "x" in all of these cases, while parse() consistently returns "y".

So this does not appear to be a chunk-boundary race. It looks like a systematic difference in duplicate/redefined statement semantics between the batch and streaming parsers.

Likely cause

The batch parser builds its statement map using normal Map.set() behavior, so a later statement with the same ID replaces the earlier one.

The streaming parser caches completed statements and skips a later completed statement when the ID is already present, effectively making that path first-definition-wins.

In particular, the completed-statement cache together with the existing guard around an already-present statement ID appears to preserve the first definition.

Expected behavior / question

I think parse() and createStreamParser() should have the same final semantics for the same completed input.

I'm not sure which duplicate-ID behavior is intended:

last definition wins

first definition wins

duplicate statement IDs are invalid and should produce a parser error

Happy to contribute the corresponding fix and regression coverage once the intended behavior is confirmed.

Additional context

I found this while building a streaming-invariance test harness for createStreamParser().

The harness currently checks valid OpenUI Lang programs across:

exhaustive single-cut partitions

exhaustive two-cut partitions

seeded multi-cut partitions

trailing-newline variants

no-prefix-crash behavior

error convergence

determinism

Those tests are currently green. Duplicate statement IDs were deliberately excluded because their intended semantics were unclear; testing that excluded case exposed this batch-vs-stream divergence.