From 70be320af9068f6c3ea92f0c988079ccb4ade889 Mon Sep 17 00:00:00 2001 From: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com> Date: Wed, 5 Aug 2026 12:51:00 +0300 Subject: [PATCH] test(qwen): pin the orphaned-record dedup key shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-extraction decoder built dedup keys with template interpolation, so a record missing sessionId or uuid produced the literal `qwen:undefined:`. Phase 8.1 changed the spelling to `qwen::` by coalescing both fields to an empty string. The intent was right — a missing identifier should contribute nothing rather than a fake value — but the change shipped with nothing behind it: no test, fixture or golden pinned either spelling, and the CLI parity golden only exercises fully-formed records, where the two are byte-identical. Nobody could tell the change from a defect. This makes the corrected shape a tested contract. The test pins the orphan spellings, asserts the collapsed key still dedups identical records, and fails against the pre-migration spelling. The decode-site comment records why, so nobody "restores" the old one. Two caveats worth knowing rather than discovering. Records with an explicit null coalesce the same way as missing ones, so two previously-distinct keys now collapse into one. And keys persisted by pre-8.1 builds for orphaned records will not match the new spelling, so such a record can be counted once more across the upgrade — the qwen corpus is not in the frozen golden set, so no shipped fixture moves. --- packages/core/src/providers/qwen/decode.ts | 5 +++ .../core/tests/providers/qwen-decode.test.ts | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/core/src/providers/qwen/decode.ts b/packages/core/src/providers/qwen/decode.ts index 9c0d6dc5..60c37bfd 100644 --- a/packages/core/src/providers/qwen/decode.ts +++ b/packages/core/src/providers/qwen/decode.ts @@ -128,6 +128,11 @@ export function decodeQwen({ records, seenKeys: liveSeen }: QwenDecodeInput): Qw const candidatesTokenCount = usage.candidatesTokenCount ?? 0 if (promptTokenCount === 0 && candidatesTokenCount === 0) continue + // Deliberate shape: the pre-extraction decoder interpolated the raw fields + // (`qwen:${entry.sessionId}:${entry.uuid}`), so a record missing sessionId + // produced the literal 'qwen:undefined:'. That spelling was a + // template-string artifact, never a contract; coalescing to '' is pinned by + // qwen-decode.test.ts ("pins the dedup-key shape ... no 'undefined' spelling"). const dedupKey = `qwen:${entry.sessionId ?? ''}:${entry.uuid ?? ''}` if (seen.has(dedupKey)) continue seen.add(dedupKey) diff --git a/packages/core/tests/providers/qwen-decode.test.ts b/packages/core/tests/providers/qwen-decode.test.ts index 92ad7878..7e6a8ca1 100644 --- a/packages/core/tests/providers/qwen-decode.test.ts +++ b/packages/core/tests/providers/qwen-decode.test.ts @@ -91,6 +91,49 @@ describe('qwen rich decode (moved to @codeburn/core)', () => { expect(again).toEqual([]) }) + it('pins the dedup-key shape for records missing sessionId/uuid — no "undefined" spelling', () => { + // The pre-extraction decoder interpolated the raw fields + // (`qwen:${entry.sessionId}:${entry.uuid}`), so a record missing sessionId + // produced the literal 'qwen:undefined:'. That spelling was a + // template-string artifact of JS coercion, NOT a contract: no test, fixture, + // or golden ever pinned it (the CLI parity golden uses fully-formed records, + // where both shapes are byte-identical), and the sibling core decoders + // (kiro, openclaw, goose) resolve missing identifiers to real fallbacks + // rather than interpolating 'undefined'. The extraction deliberately + // coalesces to '' instead: the key keeps the uuid's entropy for dedup, never + // fabricates a fake value in a key persisted to the session cache, and + // matches the shape the CLI parity golden pins for well-formed records. + // Restoring the old spelling would be a behavior change with no contract + // behind it — this test exists so nobody flips it by accident. + const noSessionId = JSON.stringify({ + uuid: 'a-orphan', + timestamp: '2026-05-16T10:02:05Z', + type: 'assistant', + message: { role: 'assistant', parts: [] }, + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5, totalTokenCount: 15 }, + }) + const noUuid = JSON.stringify({ + sessionId: 'sess-b', + timestamp: '2026-05-16T10:02:06Z', + type: 'assistant', + message: { role: 'assistant', parts: [] }, + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5, totalTokenCount: 15 }, + }) + + const { calls } = decodeQwen({ records: [noSessionId, noUuid], context }) + expect(calls).toHaveLength(2) + expect(calls[0]!.deduplicationKey).toBe('qwen::a-orphan') + expect(calls[0]!.sessionId).toBe('') + expect(calls[1]!.deduplicationKey).toBe('qwen:sess-b:') + expect(calls[1]!.sessionId).toBe('sess-b') + + // The pinned key must round-trip through the dedup set: two records that + // share a uuid and lack a sessionId still collapse to ONE call, exactly as + // the old key did for the same input. + const dup = decodeQwen({ records: [noSessionId, noSessionId], context }) + expect(dup.calls).toHaveLength(1) + }) + it('toObservations produces a schema-valid, content-free envelope', () => { const { calls } = decodeQwen({ records: RECORDS, context }) const { sessions } = toObservations(