mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 09:21:16 +00:00
fix(process): bound normalized terminal retention before allocation
Pre-count ordered normalization stages before materializing output. Charge original and normalized terminal bytes independently, omit whole over-budget chunks and preserve exact raw counts plus explicit unknown final sizes. Bound rendered normalization, retain legacy capture compatibility, and remove temporary per-match output arrays. Validation: UTF-8 surrogate boundaries, shrinking/no-op/literal rules, capture validation and public MCP expansion/equal-size controls with verified cleanup. CPU, process-family memory and token changes are unmeasured; no performance speedup claimed. Nonterminal derived payload bounds remain separate work. Fixes #3168
This commit is contained in:
1 parent
7fb0a0cc57
commit
1d826f304f
14 files changed
+894
-76
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
createBoundedProcessTextNormalizer,
|
||||
createProcessTextNormalizer,
|
||||
normalizeProcessSamples,
|
||||
normalizeProcessText,
|
||||
@@ -95,6 +96,178 @@ it("replaces caller-declared patterns literally, without expanding replacement p
|
||||
).toBe("listening on $&$&; $$; $1=alpha; tail $1");
|
||||
});
|
||||
|
||||
it("bounds ordered normalization before replacement expansion and preserves later smaller chunks", () => {
|
||||
const normalize = createBoundedProcessTextNormalizer(
|
||||
scenario({
|
||||
paths: false,
|
||||
ports: false,
|
||||
pids: false,
|
||||
patterns: [{ pattern: "a", replacement: "x".repeat(500) }],
|
||||
}),
|
||||
"/temporary",
|
||||
rootPid,
|
||||
);
|
||||
|
||||
expect(normalize("a".repeat(100), 1_000)).toEqual({ state: "over_budget" });
|
||||
expect(normalize("z", 1_000)).toEqual({
|
||||
state: "normalized",
|
||||
value: "z",
|
||||
utf8Bytes: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps ordered intermediate expansion explicit even when a later rule could shrink it", () => {
|
||||
const normalize = createBoundedProcessTextNormalizer(
|
||||
scenario({
|
||||
paths: false,
|
||||
ports: false,
|
||||
pids: false,
|
||||
patterns: [
|
||||
{ pattern: "a", replacement: "x".repeat(20) },
|
||||
{ pattern: "x", replacement: "y" },
|
||||
],
|
||||
}),
|
||||
"/temporary",
|
||||
rootPid,
|
||||
);
|
||||
|
||||
expect(normalize("a", 10)).toEqual({ state: "over_budget" });
|
||||
});
|
||||
|
||||
it("counts UTF-8 bytes across replacement boundaries like the final JavaScript string", () => {
|
||||
const splitPair = scenario({
|
||||
paths: false,
|
||||
ports: false,
|
||||
pids: false,
|
||||
patterns: [{ pattern: "\ude00", replacement: "\ude01" }],
|
||||
});
|
||||
const splitPairResult = createBoundedProcessTextNormalizer(
|
||||
splitPair,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
)("😀", 4);
|
||||
expect(splitPairResult).toEqual({
|
||||
state: "normalized",
|
||||
value: normalizeProcessText("😀", splitPair, "/temporary", rootPid),
|
||||
utf8Bytes: 4,
|
||||
});
|
||||
expect(
|
||||
splitPairResult.state === "normalized" && splitPairResult.utf8Bytes,
|
||||
).toBe(
|
||||
Buffer.byteLength(
|
||||
normalizeProcessText("😀", splitPair, "/temporary", rootPid),
|
||||
),
|
||||
);
|
||||
|
||||
const joinedPair = scenario({
|
||||
paths: false,
|
||||
ports: false,
|
||||
pids: false,
|
||||
patterns: [{ pattern: "\ude00\ud83d", replacement: "" }],
|
||||
});
|
||||
const joinedInput = "😀😀";
|
||||
const joinedExpected = normalizeProcessText(
|
||||
joinedInput,
|
||||
joinedPair,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
);
|
||||
expect(joinedExpected).toBe("😀");
|
||||
expect(
|
||||
createBoundedProcessTextNormalizer(
|
||||
joinedPair,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
)(joinedInput, Buffer.byteLength(joinedExpected)),
|
||||
).toEqual({
|
||||
state: "normalized",
|
||||
value: joinedExpected,
|
||||
utf8Bytes: Buffer.byteLength(joinedExpected),
|
||||
});
|
||||
|
||||
const emptyPattern = scenario({
|
||||
paths: false,
|
||||
ports: false,
|
||||
pids: false,
|
||||
patterns: [{ pattern: "", replacement: "\ude00" }],
|
||||
});
|
||||
const input = "\ud83d";
|
||||
const expected = normalizeProcessText(
|
||||
input,
|
||||
emptyPattern,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
);
|
||||
expect(
|
||||
createBoundedProcessTextNormalizer(
|
||||
emptyPattern,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
)(input, Buffer.byteLength(expected)),
|
||||
).toEqual({
|
||||
state: "normalized",
|
||||
value: expected,
|
||||
utf8Bytes: Buffer.byteLength(expected),
|
||||
});
|
||||
expect(
|
||||
createBoundedProcessTextNormalizer(
|
||||
emptyPattern,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
)(input, Buffer.byteLength(expected) - 1),
|
||||
).toEqual({ state: "over_budget" });
|
||||
|
||||
const loneSurrogate = scenario({
|
||||
paths: false,
|
||||
ports: false,
|
||||
pids: false,
|
||||
patterns: [{ pattern: "x", replacement: "y" }],
|
||||
});
|
||||
const loneInput = "\ud83dx";
|
||||
const loneExpected = normalizeProcessText(
|
||||
loneInput,
|
||||
loneSurrogate,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
);
|
||||
expect(
|
||||
createBoundedProcessTextNormalizer(
|
||||
loneSurrogate,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
)(loneInput, Buffer.byteLength(loneExpected)),
|
||||
).toEqual({
|
||||
state: "normalized",
|
||||
value: loneExpected,
|
||||
utf8Bytes: Buffer.byteLength(loneExpected),
|
||||
});
|
||||
});
|
||||
|
||||
it("lets a later pattern shrink text when an earlier pattern does not match", () => {
|
||||
const declared = scenario({
|
||||
paths: false,
|
||||
ports: false,
|
||||
pids: false,
|
||||
patterns: [
|
||||
{ pattern: "z", replacement: "x" },
|
||||
{ pattern: "aa", replacement: "b" },
|
||||
],
|
||||
});
|
||||
const expected = normalizeProcessText("aa", declared, "/temporary", rootPid);
|
||||
expect(expected).toBe("b");
|
||||
expect(
|
||||
createBoundedProcessTextNormalizer(
|
||||
declared,
|
||||
"/temporary",
|
||||
rootPid,
|
||||
)("aa", Buffer.byteLength(expected)),
|
||||
).toEqual({
|
||||
state: "normalized",
|
||||
value: expected,
|
||||
utf8Bytes: Buffer.byteLength(expected),
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses matchers while observing the terminal PID after launch", () => {
|
||||
let pid = -1;
|
||||
const normalize = createProcessTextNormalizer(
|
||||
|
||||
Reference in new issue
Block a user