Skip to content

Commit 22bce81

Browse files
committed
fix(bridge): toWireRecord no longer assigns undefined to session_id/phase/plugin
Caught by live-cockpit audit: 6 of 7 continuous-loop scenarios (emu/lich/naga/gorgon synthetic events, djinn.drift.observed, runtime.metrics, plus pech.ledger.appended via the loop) were silently swallowed at the TS-side schema-validate-before-emit gate. Root cause: toWireRecord checks if (record.session_id === undefined && event.session_id !== '') to copy session_id off the envelope. When event.session_id is undefined (no explicit publisher-supplied id), the comparison undefined !== '' evaluates TRUE, so the line writes record.session_id = undefined into the in-memory record. JSON.stringify drops undefined on the wire, BUT validateEvent runs against the in-memory record BEFORE serialization and rejects with "expected type string, got undefined at /session_id" — bridge logs warning and drops event. Fix: tighten all three convenience-copy guards to require a NON-EMPTY STRING source value before assignment. Same pattern applied to phase + plugin for symmetry. Direct probe verifies all 8 representative synthetic event types now make it onto stdout where 0 did before. No new top-level deps. TS tests still 394/0.
1 parent d551efa commit 22bce81

1 file changed

Lines changed: 16 additions & 3 deletions

File tree

src/observability/bridge.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,26 @@ export function toWireRecord(event: EnchantedEvent): Record<string, unknown> {
6666
// Convenience: copy session_id / phase / plugin off the envelope when the
6767
// payload didn't already provide them. Rust's GenericPayload reads these
6868
// off the top level, and well-typed variants accept them too.
69-
if (record.session_id === undefined && event.session_id !== '') {
69+
//
70+
// CRITICAL: only set the field when the source value is a non-empty
71+
// STRING. An `undefined` value here was historically writing
72+
// `record.session_id = undefined`, which JSON.stringify drops on the
73+
// wire — but the schema validator runs on the IN-MEMORY record BEFORE
74+
// serialization and rejects with "expected string, got undefined",
75+
// silently dropping any synthetic event whose publisher didn't set
76+
// session_id. (Caught by the live-cockpit audit: 6 of 7 loop scenarios
77+
// had every event swallowed at this boundary.)
78+
if (
79+
record.session_id === undefined &&
80+
typeof event.session_id === 'string' &&
81+
event.session_id !== ''
82+
) {
7083
record.session_id = event.session_id;
7184
}
72-
if (record.phase === undefined && event.phase !== undefined) {
85+
if (record.phase === undefined && typeof event.phase === 'string' && event.phase !== '') {
7386
record.phase = event.phase;
7487
}
75-
if (record.plugin === undefined && event.source !== '') {
88+
if (record.plugin === undefined && typeof event.source === 'string' && event.source !== '') {
7689
record.plugin = event.source;
7790
}
7891

0 commit comments

Comments
 (0)