Files
rea/src/domain/browserScenarioNormalization.ts
T
morluto f6905719c3 fix(browser): bound capture work and preserve observation gaps
Own service-worker CDP registrations and retry failed teardown. Admit redaction and network metadata against remaining capture capacity, retain explicit omission reasons, and preserve source URL, redirect ancestry, caller-selected content, and historical capture values. Forward CLI cancellation through provider cleanup.

Reviewed fixes: #2263, #2637, #2767, #2845, #2859, #2863, #2872, #2874, #2876, #2879, #2955, #2957, #2961, #3033.

Validation: 25 current event/network source tests passed; earlier focused browser lanes passed 201 source tests and 176 boundary tests with two skips, plus 35 Python tests. Real browser/Windows lanes were not rerun. Playwright allocation before admission remains unbounded by these retention limits.
2026-10-14 08:31:29 +08:00

69 lines
2.2 KiB
TypeScript

import { digestCanonicalValue } from "./canonicalDigest.js";
import {
BROWSER_SCENARIO_BUILT_IN_NORMALIZATION_RULES,
type BrowserScenarioArtifactKind,
type BrowserScenarioDiff,
type BrowserScenarioNormalization,
type BrowserScenarioNormalizationRule,
} from "./browserScenarioDiffValues.js";
/** Canonically order and commit a parsed browser scenario normalization policy. */
export const commitBrowserScenarioNormalization = (
input: BrowserScenarioNormalization,
): BrowserScenarioDiff["normalization"] => {
const rules = [...input.rules]
.map((rule) => ({
...rule,
artifacts: [...rule.artifacts].sort(),
}))
.sort((left, right) =>
left.rule_id < right.rule_id ? -1 : left.rule_id > right.rule_id ? 1 : 0,
);
const commitment = {
built_in_rules: [...BROWSER_SCENARIO_BUILT_IN_NORMALIZATION_RULES],
rules,
};
return {
...commitment,
sha256: digestCanonicalValue(commitment, "Browser scenario"),
};
};
/** Digest one artifact after applying committed exact-literal rules, then any artifact ordering. */
export const digestNormalizedScenarioValue = (
value: unknown,
artifact: BrowserScenarioArtifactKind,
rules: readonly BrowserScenarioNormalizationRule[],
order: (normalized: unknown) => unknown = (normalized) => normalized,
): string =>
digestCanonicalValue(
order(normalizeScenarioValue(value, artifact, rules)),
"Browser scenario",
);
export const normalizeScenarioValue = (
value: unknown,
artifact: BrowserScenarioArtifactKind,
rules: readonly BrowserScenarioNormalizationRule[],
): unknown => {
if (artifact === "screenshot") return value;
if (typeof value === "string")
return rules
.filter(({ artifacts }) => artifacts.includes(artifact))
.reduce(
(normalized, rule) =>
normalized.split(rule.match).join(rule.replacement),
value,
);
if (Array.isArray(value))
return value.map((item) => normalizeScenarioValue(item, artifact, rules));
if (typeof value !== "object" || value === null) return value;
return Object.fromEntries(
Object.entries(value).map(([key, item]) => [
key,
normalizeScenarioValue(item, artifact, rules),
]),
);
};