@looptail/sdk
Advanced tools
+6
-1
@@ -21,4 +21,9 @@ export declare const FORMAT_VERSION = "0.1"; | ||
| export declare function b64urlDecode(text: string): Buffer; | ||
| /** Sort by Unicode code point (matches Python's sort_keys / UTF-8 byte order), | ||
| * NOT the default UTF-16-code-unit order, so canonical bytes are identical | ||
| * across the JS and Python implementations. The orders differ whenever a key | ||
| * in U+E000–U+FFFF is compared against an astral-plane key. */ | ||
| export declare function compareCodePoints(a: string, b: string): number; | ||
| /** Key-sorted, whitespace-free JSON — matches Python json.dumps(sort_keys=True, | ||
| * separators=(',',':'), ensure_ascii=False) for spec-conformant values. */ | ||
| * separators=(',',':'), ensure_ascii=False) byte-for-byte. */ | ||
| export declare function canonical(value: unknown): string; | ||
@@ -25,0 +30,0 @@ export declare function eventHash(event: Partial<TrailEvent>): string; |
+24
-3
@@ -28,4 +28,22 @@ /** | ||
| } | ||
| /** Sort by Unicode code point (matches Python's sort_keys / UTF-8 byte order), | ||
| * NOT the default UTF-16-code-unit order, so canonical bytes are identical | ||
| * across the JS and Python implementations. The orders differ whenever a key | ||
| * in U+E000–U+FFFF is compared against an astral-plane key. */ | ||
| export function compareCodePoints(a, b) { | ||
| const ai = a[Symbol.iterator](); | ||
| const bi = b[Symbol.iterator](); | ||
| for (;;) { | ||
| const x = ai.next(); | ||
| const y = bi.next(); | ||
| if (x.done || y.done) | ||
| return x.done ? (y.done ? 0 : -1) : 1; | ||
| const cx = x.value.codePointAt(0); | ||
| const cy = y.value.codePointAt(0); | ||
| if (cx !== cy) | ||
| return cx - cy; | ||
| } | ||
| } | ||
| /** Key-sorted, whitespace-free JSON — matches Python json.dumps(sort_keys=True, | ||
| * separators=(',',':'), ensure_ascii=False) for spec-conformant values. */ | ||
| * separators=(',',':'), ensure_ascii=False) byte-for-byte. */ | ||
| export function canonical(value) { | ||
@@ -37,3 +55,3 @@ if (value === null || typeof value !== 'object') | ||
| const entries = Object.keys(value) | ||
| .sort() | ||
| .sort(compareCodePoints) | ||
| .map((k) => JSON.stringify(k) + ':' + canonical(value[k])); | ||
@@ -43,3 +61,6 @@ return '{' + entries.join(',') + '}'; | ||
| export function eventHash(event) { | ||
| const unsigned = {}; | ||
| // null prototype: on a plain {} an assignment to "__proto__" hits the | ||
| // prototype setter instead of creating an own property, silently dropping | ||
| // the field from the hash — a JS/Python divergence and a signature bypass. | ||
| const unsigned = Object.create(null); | ||
| for (const [k, v] of Object.entries(event)) { | ||
@@ -46,0 +67,0 @@ if (k !== 'hash' && k !== 'sig' && v !== undefined) |
+1
-1
| { | ||
| "name": "@looptail/sdk", | ||
| "version": "0.2.0", | ||
| "version": "0.2.1", | ||
| "description": "Record every AI decision as a signed, append-only trail. Every loop leaves a tail.", | ||
@@ -5,0 +5,0 @@ "license": "Apache-2.0", |
34923
3.97%548
4.98%