perf(domain): reuse canonical JSON key encodings

Reuse exact JSON key encodings within the existing 8,192-code-unit digest-buffer quantum and avoid ordinary member value frames. Preserve canonical bytes and cooperative yielding; resume objects when a pending property is omitted.

Flameox paired experiments establish a local digest-workload improvement, with complete-result/EvidenceID oracles unchanged. Full CLI acceleration and billed token reductions are not claimed.

Refs #1065.
This commit is contained in:
Ysopwnable 2026-10-11 11:40:47 +08:00 committed by GitHub
parent ea06264a56
commit 89ea2cad19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 52 additions and 13 deletions

View File

@ -21,6 +21,12 @@ describe("cooperative canonical JSON digest", () => {
),
{ payload: `${"x".repeat(8191)}𐀀${"\n\ud800".repeat(20_000)}` },
{ [`${"x".repeat(8191)}𐀀${"\n".repeat(20_000)}`]: "value" },
Object.fromEntries(
Array.from({ length: 512 }, (_, index) => [
`escaped\n\"\\𐀀${"x".repeat(32)}${index}`,
{ repeated: index },
]),
),
];
it.each(values.map((value) => ({ value })))(
"preserves complete canonical meaning (%#)",
@ -55,6 +61,18 @@ describe("cooperative canonical JSON digest", () => {
expect(steps.next().done).toBe(true);
});
it("continues object hashing when a pending property is omitted", () => {
const value: { [key: string]: JsonValue } = {
a: "x".repeat(32_768),
b: 1,
c: { retained: true },
};
const steps = canonicalJsonDigestSteps(value);
expect(steps.next().done).toBe(false);
delete value.b;
expect(complete(steps)).toBe(digestCanonicalValue(value));
});
it("rejects sparse arrays, nonfinite numbers and cycles", () => {
const sparse: JsonValue[] = [];
sparse.length = 2;

View File

@ -26,6 +26,9 @@ export function* canonicalJsonDigestSteps(
const hash = createHash("sha256");
const ancestors = new Set<object>();
const stack: DigestFrame[] = [{ kind: "value", value }];
const encodedKeys = new Map<string, string>();
// Keep reusable key encodings within one existing digest-buffer quantum.
let remainingKeyCodeUnits = 8192;
let buffered = "";
let flushed = false;
const emit = (part: string): void => {
@ -69,22 +72,22 @@ export function* canonicalJsonDigestSteps(
stack.push(frame);
continue;
}
let item: JsonValue;
if (frame.kind === "array") {
if (frame.index === frame.length) {
emit("]");
ancestors.delete(frame.value);
continue;
}
const item = frame.value[frame.index];
if (item === undefined)
const element = frame.value[frame.index];
if (element === undefined)
throw new TypeError(
"Canonical JSON digest requires dense JSON arrays",
);
if (frame.index++ > 0) emit(",");
stack.push(frame, { kind: "value", value: item });
continue;
}
if (frame.kind === "object") {
stack.push(frame);
item = element;
} else if (frame.kind === "object") {
if (frame.index === frame.keys.length) {
emit("}");
ancestors.delete(frame.value);
@ -93,25 +96,42 @@ export function* canonicalJsonDigestSteps(
const key = frame.keys[frame.index++];
if (key === undefined)
throw new TypeError("Canonical JSON object changed during hashing");
const item = frame.value[key];
if (item === undefined) continue;
const property = frame.value[key];
if (property === undefined) {
stack.push(frame);
continue;
}
if (frame.written++ > 0) emit(",");
if (key.length > 8192) {
emit('"');
stack.push(
frame,
{ kind: "value", value: item },
{ kind: "value", value: property },
{ kind: "separator" },
{ kind: "string", value: key, index: 0 },
);
continue;
}
emit(JSON.stringify(key));
let encodedKey = encodedKeys.get(key);
if (encodedKey === undefined) {
encodedKey = JSON.stringify(key);
if (encodedKey.length <= remainingKeyCodeUnits) {
encodedKeys.set(key, encodedKey);
remainingKeyCodeUnits -= encodedKey.length;
}
}
emit(encodedKey);
emit(":");
stack.push(frame, { kind: "value", value: item });
continue;
stack.push(frame);
item = property;
} else {
item = frame.value;
}
// Resume containers directly; scalar members need no temporary value frame.
if (flushed) {
flushed = false;
yield;
}
const item = frame.value;
if (typeof item === "string" && item.length > 8192) {
emit('"');
stack.push({ kind: "string", value: item, index: 0 });
@ -151,6 +171,7 @@ export function* canonicalJsonDigestSteps(
} finally {
stack.length = 0;
ancestors.clear();
encodedKeys.clear();
buffered = "";
}
}