Files
rea/src/application/ProcessNormalization.ts
T
morluto e07b81649e refactor!: simplify runtime tools and remove replay engines (#572)
* fix(hopper): report unavailable procedure body coverage

* refactor!: simplify runtime tools and remove replay engines

Keep direct inspection, capture, and comparison tools. Bind handlers to named contracts and let agents compose experiments with commands and local fixture servers.

BREAKING CHANGE: Remove controlled replay, Node characterization, finite replay-machine, managed runtime planning, and redundant string-tracing tools. Retire process replay, shims, reactive scenarios, custom checkpoints, and active browser replay and origin-scope inputs.
2026-10-05 21:15:04 +08:00

103 lines
3.2 KiB
TypeScript

import type {
ProcessSample,
ProcessScenario,
} from "../domain/processCapture.js";
/** Bucket one elapsed process-capture timestamp under scenario normalization. */
export const normalizeProcessElapsedTime = (
elapsedMs: number,
timeBucketMs: number,
): number => Math.floor(elapsedMs / timeBucketMs) * timeBucketMs;
/** Normalize one terminal/protocol payload under explicitly selected scenario rules. */
export const normalizeProcessText = (
value: string,
scenario: ProcessScenario,
temporaryRoot: string,
pid: number,
): string => {
let normalized = value;
if (scenario.normalization.paths) {
normalized = normalized.replaceAll(temporaryRoot, "<temporary-root>");
normalized = normalized.replaceAll(
scenario.working_directory,
"<working-directory>",
);
normalized = normalized.replaceAll(scenario.executable, "<executable>");
for (const [index, root] of scenario.filesystem_observation_paths.entries())
normalized = normalized.replaceAll(
root,
`<filesystem-root-${String(index)}>`,
);
}
if (scenario.normalization.pids)
normalized = normalizePidTokens(normalized, [pid]);
if (scenario.normalization.ports)
normalized = normalized.replaceAll(/(?<=[:=])\d{2,5}\b/g, "<port>");
for (const pattern of scenario.normalization.patterns)
normalized = normalized.replaceAll(pattern.pattern, pattern.replacement);
return normalized;
};
export const normalizeProcessSamples = (
samples: readonly ProcessSample[],
scenario: ProcessScenario,
rootPid: number,
): readonly ProcessSample[] => {
const identifiers = [
rootPid,
...samples.flatMap((sample) => [
sample.pid,
sample.parent_pid,
sample.process_group_id ?? 0,
sample.session_id ?? 0,
]),
];
const mapping = new Map<number, number>();
for (const identifier of identifiers)
if (identifier > 0 && !mapping.has(identifier))
mapping.set(identifier, mapping.size + 1);
const normalizeCommand = (command: string): string => {
const normalized = normalizeProcessText(
command,
scenario,
"<no-temporary-root>",
rootPid,
);
const withNormalizedPids = scenario.normalization.pids
? normalizePidTokens(normalized, identifiers)
: normalized;
return withNormalizedPids.trim();
};
return samples.map((sample) => ({
at_ms: normalizeProcessElapsedTime(
sample.at_ms,
scenario.normalization.time_bucket_ms,
),
pid: mapping.get(sample.pid) ?? 1,
parent_pid: mapping.get(sample.parent_pid) ?? 0,
process_group_id:
sample.process_group_id === null
? null
: (mapping.get(sample.process_group_id) ?? 0),
session_id:
sample.session_id === null ? null : (mapping.get(sample.session_id) ?? 0),
command: normalizeCommand(sample.command),
}));
};
const normalizePidTokens = (
value: string,
identifiers: readonly number[],
): string => {
const tokens = [...new Set(identifiers)]
.filter((identifier) => Number.isSafeInteger(identifier) && identifier > 0)
.sort((left, right) => right - left)
.map(String);
if (tokens.length === 0) return value;
return value.replace(
new RegExp(`(?<!\\d)(?:${tokens.join("|")})(?!\\d)`, "gu"),
"<pid>",
);
};