feat(LinuxTentacleE2E): Phase 12.L.E.16 — upgrade-events.jsonl format contract pin
Summary
Pins the wire format the server's UI timeline parser consumes from upgrade-events.jsonl. Every E1.h-style success ALREADY emits these events; what's never been pinned is the format contract — server-side parser drops malformed lines silently → operator sees frozen timeline.
Why this matters
Without this test, regressions in any of the following ship invisibly:
- JSON malformation (unescaped quotes, missing commas, wrong shape)
- Field renames (
msg→message,kind→type) - Phase tag drift (
"A"→"PhaseA") -
Missing terminal events (
successabsent → UI hangs in "in-progress" forever) - Out-of-order events (success before scope-exec → broken causality)
- Non-UTC timestamps (UI ordering by timestamp breaks)
Wire format pinned
{"t":"2026-04-22T02:57:04Z","phase":"A","kind":"method-try","msg":"apt: update"}
(.sh line 206 docstring + line 249 printf)
Test approach
Run E1.h-Linux end-to-end, then PARSE every line of the resulting upgrade-events.jsonl as JSON (using System.Text.Json — the same parser the server uses).
Assertions
- Every line is valid JSON (parser throws → assertion fails)
- Every event has the four required fields
{t, phase, kind, msg} - Phase values are EXACTLY
"A"or"B"(not"1","a","PhaseA") - Timestamps end with
Z(UTC) and parse asDateTimeOffset - Expected kind sequence appears in order:
-
start→method-selected→scope-exec(Phase A) -
swapped→restart-start→healthz-pass→success(Phase B)
-
- Last event is
success(terminal-state contract — UI flips from in-progress → completed only on this kind)
Infrastructure additions
LinuxLifecycleContext:
-
EventsFilePathproperty —{STATE_DIR}/upgrade-events.jsonl -
ReadUpgradeEvents()→IReadOnlyList<UpgradeEvent>— parses every line; throws on malformed lines (the throw IS one of the assertions — server-side parser would silently drop that same line) -
UpgradeEventrecord(T, Phase, Kind, Msg)— mirrors the.sh'semit_eventJSON shape verbatim
Fidelity tier
.sh + real systemd + real bash + real .jsonl file emitted by real emit_event call site. Composes with E1.h's fixture entirely; only adds parse + sequence assertions on top.
Expected runtime: ~5s (same as E1.h + a few ms for parse).
Test plan
-
Linux E2E workflow runs (manual workflow_dispatchafter merge) -
E1uEventsContract_AfterSuccess_AllLinesValidJsonInExpectedOrderpasses within ~5s -
No regression on existing 16 Linux E2E tests