Skip to content

Commit 8b0520f

Browse files
grypezclaude
andcommitted
fix(kernel-utils): use type-tagged encoding in metadataKey to prevent conflation
JSON.stringify maps undefined, NaN, Infinity, and -Infinity all to null, so sections with e.g. { cost: Infinity } and { cost: null } produced identical keys and were incorrectly collapsed into one germ. Replace the plain JSON.stringify(entries) with encodeMetadataEntry, which includes a typeof tag in each tuple so all of these distinct values produce distinct keys. BigInt metadata values no longer throw at serialization time either. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 88bc8a3 commit 8b0520f

1 file changed

Lines changed: 29 additions & 3 deletions

File tree

packages/kernel-utils/src/sheaf/sheafify.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,35 @@ import type {
3232
Sheaf,
3333
} from './types.ts';
3434

35+
type EncodedEntry = [key: string, type: string, value: unknown];
36+
37+
const encodeMetadataEntry = (key: string, value: unknown): EncodedEntry => {
38+
if (value === undefined) {
39+
return [key, 'undefined', null];
40+
}
41+
if (typeof value === 'bigint') {
42+
return [key, 'bigint', String(value)];
43+
}
44+
if (typeof value === 'number') {
45+
if (Number.isNaN(value)) {
46+
return [key, 'NaN', null];
47+
}
48+
if (value === Infinity) {
49+
return [key, '+Infinity', null];
50+
}
51+
if (value === -Infinity) {
52+
return [key, '-Infinity', null];
53+
}
54+
}
55+
return [key, typeof value, value];
56+
};
57+
3558
/**
3659
* Serialize metadata for equivalence-class keying (collapse step).
3760
*
61+
* Uses type-tagged encoding so that values JSON.stringify conflates
62+
* (undefined, null, NaN, Infinity, -Infinity) produce distinct keys.
63+
*
3864
* @param metadata - The metadata value to serialize.
3965
* @returns A string key for equivalence comparison.
4066
*/
@@ -43,9 +69,9 @@ const metadataKey = (metadata: Record<string, unknown>): string => {
4369
if (keys.length === 0) {
4470
return 'null';
4571
}
46-
const entries = Object.entries(metadata).sort(([a], [b]) =>
47-
a.localeCompare(b),
48-
);
72+
const entries = Object.entries(metadata)
73+
.sort(([a], [b]) => a.localeCompare(b))
74+
.map(([key, val]) => encodeMetadataEntry(key, val));
4975
return JSON.stringify(entries);
5076
};
5177

0 commit comments

Comments
 (0)