Merge pull request #1291 from morluto/fix/process-port-normalization-sentence-period

fix(process): normalize ports that end a sentence
This commit is contained in:
Kaoru0822
2026-10-09 07:34:54 -07:00
committed by GitHub
5 changed files with 42 additions and 6 deletions
+5 -2
View File
@@ -112,8 +112,11 @@ same admitted PTY chunks rather than a second unbounded stream.
Port normalization recognizes explicit `port`, `tcp_port`, `udp_port`, and
`listen` fields, URL authorities, IP endpoints, and `localhost` endpoints, with
ports from 0 through 65535. It preserves ordinary counters, dimensions, file
line numbers, and ambiguous bare host labels. Use explicit literal `patterns`
ports from 0 through 65535. A port may end a sentence, as in `port: 8080.`;
a period followed by a letter or digit continues a decimal, version, or host
name instead. Each PTY chunk is normalized on its own, so a port whose period
ends the chunk is left unchanged because its continuation is not yet known. It preserves ordinary counters, dimensions, file line numbers,
and ambiguous bare host labels. Use explicit literal `patterns`
for a different application-specific spelling. Set `ports: false` to preserve
all endpoint numbers in comparison text. Captures commit the port-normalization
version so older broad numeric normalization cannot silently compare as the
+1 -1
View File
@@ -85,7 +85,7 @@ export const processComparisonContract = (
limits: scenario.limits,
normalization: scenario.normalization,
...(scenario.normalization.ports
? { port_normalization_version: "contextual-endpoints-v1" }
? { port_normalization_version: "contextual-endpoints-v2" }
: {}),
});
@@ -99,6 +99,34 @@ it("normalizes only contextual ports before overlapping PID values and honors di
).toBe(input);
});
it("normalizes ports that end a sentence but not decimal or host continuations", () => {
const input = [
"Listening on port: 8080.",
"Server at http://localhost:8080. Ready",
"Bound [::1]:3000.",
"listen=9000.\n",
"port: 80.5 localhost:8080.5 localhost:8080.example.test",
"port: 8080.β localhost:8080.é port: 8080é",
].join("\n");
expect(normalizeProcessText(input, scenario({}), "/temporary", rootPid)).toBe(
[
"Listening on port: <port>.",
"Server at http://localhost:<port>. Ready",
"Bound [::1]:<port>.",
"listen=<port>.\n",
"port: 80.5 localhost:8080.5 localhost:8080.example.test",
"port: 8080.β localhost:8080.é port: 8080é",
].join("\n"),
);
});
it("keeps a port before a chunk-final period, whose continuation is unknown", () => {
for (const chunk of ["port: 80.", "http://localhost:8080.", "[::1]:3000."])
expect(
normalizeProcessText(chunk, scenario({}), "/temporary", rootPid),
).toBe(chunk);
});
it("normalizes representative process identities and preserves null, zero, and unrelated numbers", () => {
const before = JSON.stringify(samples);
const normalized = normalizeProcessSamples(samples, scenario({}), rootPid);
+7 -2
View File
@@ -41,9 +41,14 @@ export const normalizeProcessText = (
return normalized;
};
// A port may end a sentence, but a period followed by a letter, digit, or
// underscore in any script continues a decimal, version, or host name and is
// not a port boundary.
// PTY chunks are normalized independently, so a period that ends the chunk
// stays unnormalized: its continuation may arrive in the next chunk.
const normalizePortTokens = (value: string): string => {
const endpointNormalized = value.replaceAll(
/(\b[a-z][a-z0-9+.-]*:\/\/[^\s/?#"'<>]+|\blocalhost|\b(?:\d{1,3}\.){3}\d{1,3}|\[[\da-f:.%]+\]):(\d{1,5})(?![\w.@:])/giu,
/(\b[a-z][a-z0-9+.-]*:\/\/[^\s/?#"'<>]+|\blocalhost|\b(?:\d{1,3}\.){3}\d{1,3}|\[[\da-f:.%]+\]):(\d{1,5})(?![\p{L}\p{N}_@:]|\.(?:[\p{L}\p{N}_]|$))/giu,
(match: string, endpoint: string, port: string) => {
if (Number(port) > 65_535) return match;
const isEndpoint = endpoint.includes("://")
@@ -54,7 +59,7 @@ const normalizePortTokens = (value: string): string => {
},
);
return endpointNormalized.replaceAll(
/(\b(?:port|tcp_port|udp_port|listen)\b["']?\s*[:=]\s*)(\d{1,5})(?![\w.])/giu,
/(\b(?:port|tcp_port|udp_port|listen)\b["']?\s*[:=]\s*)(\d{1,5})(?![\p{L}\p{N}_]|\.(?:[\p{L}\p{N}_]|$))/giu,
(match: string, prefix: string, port: string) =>
Number(port) <= 65_535 ? `${prefix}<port>` : match,
);
@@ -63,7 +63,7 @@ itWithCaptureCapability(
),
).toBe(true);
expect(capture.manifest.comparison_contract).toMatchObject({
port_normalization_version: "contextual-endpoints-v1",
port_normalization_version: "contextual-endpoints-v2",
});
expect(capture.exit).toMatchObject({ code: 0, reason: "exited" });
expect(capture.cleanup).toMatchObject({