Skip to content

Show sender-selected path-hash width on channel messages - #212

Merged
dborup merged 3 commits into
masterfrom
codex/channel-sender-path-hash
Oct 6, 2026
Merged

dborup merged 3 commits into
masterfrom
codex/channel-sender-path-hash

Conversation

@dborup

@dborup dborup commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Summary

Adapt the sender-selected path-hash display from Kpa-clawbot/CoreScope PR Kpa-clawbot#2089 for this fork. Channel messages now show Sent with: N-byte from the transmission's raw frame header instead of presenting relayed observation-path widths as the sender's choice. The existing observation evidence is retained internally.

The fork also uses the same header rule in View packet. A zero-hop flood still encodes the sender's width and displays it; a direct zero-hop marker does not encode a width and remains unlabeled. This avoids the cross-view inconsistency in a direct port of the upstream change.

Verification

  • Full cmd/server Go suite and go vet ./... passed locally.
  • internal/packetpath tests passed, including 1/2/3-byte, transport, zero-hop flood, direct-zero, TRACE and malformed headers.
  • Frontend helper suite: 709 passed, 0 failed.
  • Channel evidence/cache unit suite: 13 passed, 0 failed.
  • Channel browser regression: 8 passed, 0 failed, including 375 px layout, WS/REST merge, and zero-hop semantics.
  • JS syntax, gofmt, and staged diff checks passed.

Scope and limitations

  • Adds senderPathHashSize to channel-message API rows (0 means unknown); documents its per-frame meaning.
  • No schema, config, workflow, or deployment change.
  • The label reports what this particular frame encoded, not a permanent sender setting. Different transmissions from one sender may use different widths.
  • Tests above are local; GitHub CI and staging have not yet been verified.

@dborup dborup left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed commit fd7e55df. One P2 finding should be addressed before merge: the packet-detail hash-size summary can describe the original transmission while the raw-byte breakdown describes a different selected observation. See the inline reproduction and suggested correction.

Validation performed independently: relevant server/channel tests and the packetpath suite passed with the race detector; 709 frontend-helper checks, 13 channel evidence/cache checks, and all 8 channel browser checks passed. A separate browser reproduction confirmed the mismatch below and compared it with the base packet-detail implementation. The full server suite and staging were not rerun as part of this review.

Comment thread public/packets.js Outdated
const plOff = getPathLenOffset(pkt.route_type);
const rawPathByte = pkt.raw_hex ? parseInt(pkt.raw_hex.slice(plOff * 2, plOff * 2 + 2), 16) : NaN;
const hashSize = (isNaN(rawPathByte) || (rawPathByte & 0x3F) === 0) ? null : ((rawPathByte >> 6) + 1);
const hashSize = senderPathHashSize(pkt.raw_hex);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Read the hash width from the selected observation

renderDetail uses effectivePkt for the selected observation's raw bytes and field table, but this line reads the original transmission's pkt.raw_hex. Reproduced in Chromium with an original zero-hop flood beginning 1540 and a selected direct-zero-hop observation beginning 1600: the summary displays Hash Size: 2 bytes, while the byte table correctly displays hash_count=0 (no encoded hash size). With the base packet-detail code, the summary did not emit a width for this zero-hop case. This breaks the PR's per-frame/zero-hop display contract when a transmission has multiple observation variants. Use the same raw-byte source as the displayed observation (effectivePkt.raw_hex || pkt.raw_hex) and add a browser regression that selects between the two observations, asserting that the summary and byte table agree.

@dborup dborup left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review — changes still recommended before merge

Rechecked head fd7e55df8c71a79a3a73e8a4aea8ebfc10820412 against master 8bafcdf2a6bb0014127bce4822c8ad0399bd2f12. The PR head is unchanged since the earlier review. All 12 changed files in the local review export were verified byte-for-byte against this commit.

1. Existing P2: selected-observation hash-size mismatch remains

The original inline finding is still applicable; I am linking it rather than duplicating the thread. The Chromium reproduction still shows Hash Size: 2 bytes for a selected direct-zero-hop observation while its byte table says no encoded hash size. The original transmission begins 1540, the selected observation begins 1600; the summary still reads pkt.raw_hex rather than the selected observation's effective bytes. The base implementation emits no summary width for this zero-hop case. Use the same effective raw-byte source for the summary and byte table and add an observation-switching regression.

2. New P2: shared helper is missing from ESLint globals

The failed CI job stops in frontend lint, with three senderPathHashSize is not defined errors. These reproduce locally in public/channels.js:74 and public/packets.js:3415,3770. The corresponding base files pass the same check. The helper exists in app.js at runtime; this is a missing cross-file lint declaration, not a claim that the browser cannot load the helper. Details and the suggested correction are attached inline.

Independently rerun in this pass

  • Targeted server channel/hash-size tests with -race -count=1: passed.
  • Full internal/packetpath suite with -race -count=1: passed.
  • Frontend helper checks: 709 passed, 0 failed.
  • Channel evidence/cache checks: 13 passed, 0 failed.
  • Channel browser regression on a disposable local server: 8 passed, 0 failed, including the 375 px layout and WS/REST cases.
  • Separate selected-observation browser reproduction: the existing mismatch is still present.
  • Frontend lint: 3 errors, matching CI. The downstream CI Playwright and Docker jobs were skipped, not passed.

The full server/ingestor suites and staging/production were not rerun in this pass. No source changes, pushes, merges or deployments were made. Please address both P2 items and rerun the full CI pipeline before merging.

Posted as a comment review because the signed-in account is also the PR author.

Comment thread public/channels.js
}

function senderSizeFromRawHex(rawHex) {
return typeof senderPathHashSize === 'function' ? senderPathHashSize(rawHex) : null;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Register the shared senderPathHashSize helper with ESLint

This new cross-file call, plus the two new calls in packets.js at lines 3415 and 3770, fails the mandatory no-undef gate because senderPathHashSize is absent from .eslintrc.json's shared globals. CI run 37190814810 fails with exactly these three errors, and running ESLint locally on the changed frontend files reproduces them; the corresponding base files pass. The runtime function is present in app.js, so declare it as a read-only shared global following the existing helper conventions (or use an explicitly exported namespace consistently), without disabling no-undef. Then rerun frontend lint and the complete CI pipeline: the current failure prevents the downstream browser and Docker checks from running.

dborup added 2 commits October 5, 2026 21:01
…vation

Addresses both P2 findings on PR #212, plus a red test gate neither review
reported.

1. Frontend lint (no-undef). senderPathHashSize lives in app.js but was not
   declared in .eslintrc.json, so the three cross-file calls in channels.js
   and packets.js failed the mandatory no-undef gate. Declared as a read-only
   shared global alongside the other app.js helpers.

2. Selected-observation mismatch. renderDetail built the byte table from the
   selected observation (effectivePkt) but read the "Hash Size" summary from
   the original transmission's raw_hex. Observations of one transmission carry
   their own frames (routes.go fills observations[].raw_hex), so a selected
   direct zero-hop observation showed "Hash Size: 2 bytes" above a byte table
   that said it encodes no width. The summary now reads the same frame.

3. test-packets.js was left stale by the original change: the two
   buildFieldTable hash_size tests still expected the pre-PR "direct advert"
   wording and a 4-byte hash size, so `sh test-all.sh` -- a CI gate that runs
   before lint -- was red. Replaced with tests for the new contract, and
   restored the information the new wording had dropped: the byte breakdown now
   distinguishes TRACE SNR path bytes, an invalid 0b11 width field, and
   sendZeroHop's 0x00 direct marker instead of calling all three "no encoded
   hash size".

Also replaced the hand-rolled senderPathHashSize stub in
test-channels-observed-path-hash-size.js with the real app.js helper; the stub
claimed a 3-byte width for '59C0', which the shipped rule reports as unknown.

New browser regression test-packet-detail-sender-hash-size-obs-e2e.js switches
between a zero-hop flood observation and a direct zero-hop one and asserts the
summary and byte table agree; it fails on the pre-fix code.
@adminopenclaw8-sketch

Copy link
Copy Markdown
Collaborator

Rapport — CS-Macmini PR#212 runde 2 — head 0cdfc66

Review feedback addressed (commit 0cdfc66c)

  1. New P2 — shared helper missing from ESLint globals (follow-up review §2, inline on public/channels.js:74). senderPathHashSize is now declared "readonly" in .eslintrc.json, next to the other app.js helpers (scopeCellHtml, setupPullToReconnect, …). no-undef is untouched and nothing is disabled. Frontend lint goes from 3 errors to 0 errors. [T]
  2. Existing P2 — selected-observation hash-size mismatch (review §1, inline on public/packets.js:3415, thread r4176889941). renderDetail now reads the summary width from effectivePkt.raw_hex || pkt.raw_hex, i.e. the same frame buildFieldTable is handed one line below. The reported case reproduces exactly on the old code and is gone on the new: a selected direct zero-hop observation no longer shows Hash Size: 2 bytes above a byte table that says no encoded hash size. The suggested observation-switching browser regression is added as test-packet-detail-sender-hash-size-obs-e2e.js and registered in the Playwright job. [T][K]
  3. Not reported by either review — sh test-all.sh was already red. The original change left test-packets.js stale, so a CI gate that runs before frontend lint was failing on fd7e55df too (node test-packets.js → 125 passed, 3 failed; two of those caused by this PR, the third since fixed on master). The two stale cases expected the pre-PR hash_count=0 (direct advert) wording and a hash_size=4. Replaced with tests for the new contract. [T][A]
  4. Information the new wording had dropped, restored. senderPathHashSize collapses every "no width" case to null, and the byte breakdown printed one sentence for all of them. In a hex-breakdown view that is a regression, so Path Length now distinguishes the three cases: TRACE path bytes (SNR, not a width), a 0b11 width field (no 4-byte width exists — cmd/server/observed_path_hash_sizes.go recognises only 1/2/3), and sendZeroHop's 0x00 direct marker. The 1–3 contract itself is unchanged, so SenderHashSize in Go and the JS helper still agree. [T][K][A]
  5. Hand-rolled helper stub replaced with the real one. test-channels-observed-path-hash-size.js stubbed senderPathHashSize as rawHex === '59C0' ? 3 : null. The shipped rule reports 59C0 as unknown (0xC0 >> 6 == 3), so the sender-width merge assertions were passing against a contract nothing ships. The test now lifts the real app.js declarations into their own context and throws if app.js stops declaring them. [T][K]
  6. Dead local removed. hashSizeVal in buildFieldTable became unused when the description switched to encodedHashSize; it was a PR-introduced no-unused-vars warning. Removed (public/ warnings 89 → 88). [T]
  7. origin/master merged at c6b356de (newer than the requested 0572e7f9) as merge commit b3366cd0. No rebase, no force-push; the push was a fast-forward. No conflicts. Everything below was run on the merged tree.

Tests [T]

All on the merged tree at 0cdfc66c, macOS, Go 1.27.0, Node 22.

Suite Result
npx eslint public/*.js (eslint@8, npm install --no-save, as in CI) 0 errors, 88 warnings (was 3 errors / 89)
sh test-all.sh 219 files, 219 passed, 0 failed (was 1 failed)
node test-frontend-helpers.js 709 passed, 0 failed
node test-channels-observed-path-hash-size.js 13 passed, 0 failed
node test-packets.js 143 passed, 0 failed (was 125/3)
cmd/server: go vet ./... + go test ./... clean; ok 37.7s
cmd/server: go test -race -count=1 -run 'ChannelSenderHashSize|ChannelMessages|HashSize|SenderHashSize' ok 20.2s
internal/packetpath: go test -race -count=1 ./... ok 1.4s
cmd/ingestor: go vet ./... + go test ./... clean; ok 103s
scripts/check-xss-sinks.sh --diff origin/master rc=0

Browser suites ran against a disposable local Go server on a high loopback port with a scratch copy of e2e-fixture.db, prepared the same way the Playwright job does it (freshen, the Kpa-clawbot#1486 and Kpa-clawbot#1791 seed rows, corescope-migrate, then the Kpa-clawbot#2073 / #199 / #245 seeds). Nothing outside that server was contacted; the server was stopped by port lookup afterwards.

E2E Result
test-packet-detail-sender-hash-size-obs-e2e.js (new) 3 passed, 0 failed
test-channels-observed-path-hash-size-e2e.js 8 passed, 0 failed
test-channels-selection-flow-e2e.js 8 passed, 0 failed
test-channels-client-state-152-e2e.js 6 passed, 0 failed
test-channel-issue-1087-e2e.js / -1111-e2e.js 3 / 2 passed, 0 failed
test-channel-fluid-e2e.js 15 passed, 0 failed
test-issue-147-packets-url-obs-e2e.js 12 passed, 0 failed
test-issue-180-packets-url-modal-e2e.js 12 passed, 0 failed
test-issue-1128-packets-layout-e2e.js 5 passed, 0 failed
test-issue-96-hide-control-e2e.js 10 passed, 0 failed
test-packet-trace-alignment-e2e.js 17 checks passed
test-e2e-playwright.js 132/135 passed, 3 skipped

Mutants [K]

One per finding; each was reverted immediately after.

# Mutation Result
M1 Delete "senderPathHashSize": "readonly" from .eslintrc.json eslint back to 3 errors — channels.js:78, packets.js:3520, packets.js:3875, matching the reported CI failure
M2 Summary back to senderPathHashSize(pkt.raw_hex) new E2E 1 passed, 2 failed: direct zero-hop still shows Hash Size: 2 bytes and byte table encodes no width but summary says "2 bytes" — the reported symptom verbatim
M3 Same revert, fast suite test-packets.js 142/1 — the source guard on the summary's frame fires without needing a browser
M4 Disable the TRACE branch in pathDescription does not read a hash width out of TRACE SNR bytes fails
M5 Disable the 0b11 invalid-width branch marks a 0b11 width field as invalid rather than hiding it fails
M6 Restore the pre-PR (direct advert) wording in the fallback keeps the direct zero-hop marker distinct from an invalid width fails
M7 Put the '59C0' stub hex back in test-channels-observed-path-hash-size.js delta cache absorbs later evidence… fails (undefined !== 3) — proof the real helper is now the one under test

CI per job

No CI signal was produced — not because of a test, but because no hosted runner was ever assigned. The pipeline run for 0cdfc66c was attempted three times and every attempt ended the same way:

Job Attempt 1 Attempt 2 Attempt 3
✅ Go Build & Test cancelled cancelled cancelled
🎭 Playwright E2E Tests skipped skipped skipped
🏗️ Build & Publish Docker Image skipped skipped skipped
📦 Release Artifacts skipped skipped skipped
🚀 Deploy Staging skipped skipped skipped
📝 Publish Badges & Summary skipped skipped skipped

Every attempt of the Go Build & Test job shows runner_name="", 0 steps executed, and roughly 15 minutes queued before being cancelled, with the check annotation:

The job was not acquired by Runner of type hosted even after multiple attempts

So no step ran — not checkout, not the Go suite, not sh test-all.sh, not frontend lint. The downstream jobs were skipped on the cancelled dependency, exactly as in the previously reported run; this is not a repeat of that run's lint failure, and it is not the known flaky #256 Hash Stats sort either.

The failure is not specific to this branch: in the same window Squad Heartbeat (Ralph) produced identically shaped zero-step cancellations on master (twice) and on perf/node-paths-cpu, while unrelated runs that did get a runner completed normally. It is hosted-runner starvation on the repository, so I stopped after the third attempt rather than keep burning attempts in a bad window.

CI therefore still needs to be rerun once runners are available. Everything in the Tests section above was run locally on the merged tree and is the only verification currently backing this head.

Analysis notes [A]

  • The mismatch is reachable in production, not only in a stub: cmd/server/routes.go deliberately gives each observation its own wire bytes (observations[].raw_hex, upstream bug(packets): detail API discards distinct per-observation wire bytes Kpa-clawbot/CoreScope#1999 enrichment) precisely because observations of one transmission can differ in their path bytes. That is why the summary and the byte table could describe different frames.
  • The 1–3 byte width range is this fork's established model — observedPathHashSizeMask accepts only 1/2/3-byte hops — so SenderHashSize mapping 0b11 to unknown is consistent, and I kept it rather than widening the contract. Only the breakdown's text was made specific again.
  • gofmt -l cmd internal lists 62 files, byte-for-byte the same list on origin/master (c6b356de) with local Go 1.27.0. Pre-existing and not a CI gate; nothing on this branch contributes to it.

Remaining / not done

  • renderObservedPathHashBadge and OBSERVED_PATH_HASH_TOOLTIP in channels.js are now reachable only from the test export, since renderMessages switched to the sender badge. That matches the PR's stated intent ("the existing observation evidence is retained internally"), so I left them, but whether to keep an unrendered renderer is a call for the author before merge.
  • The ADVERT row Advertised Hash Size is now omitted when the width field is 0b11 (it used to print "4 bytes"). Intentional under the 1–3 contract, no test asserts the old text.
  • 88 no-unused-vars warnings remain in public/, all pre-existing. app.js reporting senderPathHashSize is defined but never used is the same warning every other shared app.js helper produces (payloadTypeColor, scopeCellHtml, …); no action taken.
  • Other frontend sites still derive a width with the bare (pathByte >> 6) + 1, so they can still print 4: the packets table's Hop Bytes column (packets.js:2454, :2501, :2538), hop-filter.js:57, node-adverts.js:172 and app.js:98. The detail panel is now the only consistent one. Pre-existing at all of those sites and outside this PR's diff — worth its own issue rather than a widening of this change.
  • No deployment or staging verification. PR status is unchanged — still open, not marked ready differently, nothing merged or closed.

@dborup

dborup commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

Review — CS-Minimax PR#212 — head 0cdfc66

Dom: APPROVE with nits

Independent read-only review. Head verified as 0cdfc66c via git ls-remote before and after the pass; unchanged. All work was done on git archive exports of the head and of the merge result git merge-tree --write-tree origin/master 0cdfc66c (tree 875c88f8) against origin/master = f91339f2. No pushes, no PR changes, no staging or production access.

Both P2 items from the two earlier formal reviews are fixed, and each is backed by a test that is red on the pre-fix code and green on this head. The three undisclosed items the round-2 report added (stale test-packets.js, the collapsed "no width" wording, the hand-rolled helper stub) are all real and correctly handled. Nothing below blocks merge.

Findings

# Sev Where Finding
N1 P3 cmd/server/db.go:3583, :3591 substr(t.raw_hex, 1, 12) is exactly the minimum a transport route needs (path byte at offset 5 → 6 bytes). No test exercises a transport-route frame through GetChannelMessages, so the length is unguarded: shortening it to 1, 4 survives the entire Go suite. Shipped value is correct; this is a coverage gap, not a defect.
N2 P3 public/packets.js:3875–3897 The Path Length row now mixes two offset sources. The displayed byte and hash_count come from off, derived from pkt.route_type; hash_size= comes from senderPathHashSize(buf), derived from the frame's own header byte. They disagree when the two disagree on transport-ness. Observations carry their own raw_hex but not their own route_type, so effectivePkt.route_type is always the transmission's.
N3 nit public/channels.js:14, :60 renderObservedPathHashBadge and OBSERVED_PATH_HASH_TOOLTIP are now reachable only from the test export. Already disclosed in the round-2 report and explicitly left as an author call; noting it only to confirm it. ESLint does not flag it because the test-export line counts as a reference.

N2, demonstrated through the suite's own buildFieldTable sandbox — same frame, only route_type varied [T]:

route_type=1, frame 0x15 (flood):       ["0x40", "hash_size=2 bytes, hash_count=0"]   consistent
route_type=0 (transport), frame 0x15:   ["0xEF", "hash_size=2 bytes, hash_count=47"]  row contradicts itself
route_type=1, frame 0x14 (transport):   ["0x11", "hash_size=2 bytes, hash_count=17"]  row contradicts itself

Reachability is narrow: it needs an observation frame whose route differs from the transmission's in transport-ness (0/3 vs 1/2), not merely in route, so the flood-vs-direct case the original P2 used does not trigger it. Pre-fix, buildFieldTable derived the width from pathByte0 at off and was internally consistent, so this is newly introduced — but the honest reading is that the frame is authoritative for its own bytes and off is the stale half, and fixing off is outside this PR. Worth tracking separately rather than widening this change. [A]

The points verified

1. ESLint — confirmed. npx eslint public/*.js with eslint@8 installed via npm install --no-save exactly as the CI step does: 0 errors, 88 warnings. senderPathHashSize is declared "readonly" in .eslintrc.json among the other shared app.js helpers; no-undef is untouched and nothing is disabled. Mutant M1 (delete that one line) restores 3 errors at channels.js:78, packets.js:3524, packets.js:3878 — the same three the earlier review reported from CI. CI's own frontend-lint step also passed on this head (see CI section). [T][K]

2. The P2 — fixed, with a red-before test. renderDetail:3524 now reads senderPathHashSize(effectivePkt.raw_hex || pkt.raw_hex), which is the same frame buildFieldTable(effectivePkt.raw_hex ? effectivePkt : pkt, …) receives at :3680. I ran the new test-packet-detail-sender-hash-size-obs-e2e.js against a local Go server: 3 passed, 0 failed. It is a genuine regression test, not a tautology — it asserts the summary and the byte table agree in both directions and across an in-place observation switch.

Mutant M2, restoring senderPathHashSize(pkt.raw_hex): 1 passed, 2 failed, with the originally reported symptom verbatim — direct zero-hop still shows Hash Size: 2 bytes and byte table encodes no width but summary says "2 bytes". The fast source guard in test-packets.js also fires under the same mutant (143 → 142/1), so the regression is caught without a browser. [T][K]

3. The other round-2 items — all three correct.

  • test-packets.js was genuinely stale. On the round-1 head fd7e55df, node test-packets.js gives 125 passed, 3 failed: buildFieldTable hash_size calculation, buildFieldTable hash_size shown when hash_count > 0 (both caused by the PR) and buildGroupRowHtml renders multi-count group with expand arrow (unrelated, green on this merged tree). So a gate running before frontend lint was already red on round 1. Now 143 passed, 0 failed. [T]
  • The "no width" cases. pathDescription splits the three genuinely-distinct cases: TRACE path bytes (SNR, not a width), a 0b11 width field, and sendZeroHop's 0x00 direct marker. Mutant M4 (collapse the TRACE and 0b11 branches into the generic sentence) fails exactly the two tests that assert them. Treating 0b11 as "no valid width" rather than 4 bytes matches this fork's model — observedPathHashSizeMask accepts only 1/2/3 — so the Go and JS contracts stay aligned. [T][K][A]
  • The hand-rolled stub is gone. loadAppHelper extracts the real senderPathHashSize (with isTransportRoute and getPathLenOffset) out of public/app.js by brace matching and runs it in its own context. The claim that it fails loudly holds: mutant M3, renaming the function in app.js, makes the suite throw rather than silently pass. The old '59C0' ? 3 : null stub did test a contract nothing ships — 0xC0 >> 6 == 3, so the real helper reports it unknown. The replacement fixture '1580DEADBEEF' is a correct 3-byte case. [T][K][A]

4. The merge commit is clean. b3366cd0 merges c6b356de into fd7e55df. Recomputing the merge of its two parents with git merge-tree --write-tree yields tree 94249ca5, which equals the commit's actual tree exactly — a pure auto-merge with no manual edits smuggled in, and no conflicts. The merge into current origin/master is also clean. [A]

5. Channel and packets E2Es — green. All against a local Go server on a high loopback port, with a scratch copy of e2e-fixture.db prepared the way the Playwright job does it: freshen, the two seed rows, corescope-migrate, then the 2073 / 199 / 245 seeds. Served uninstrumented public/ rather than public-instrumented. Nothing outside that server was contacted; both servers were stopped by port lookup afterwards.

E2E Result
test-packet-detail-sender-hash-size-obs-e2e.js (new) 3 passed, 0 failed
test-channels-observed-path-hash-size-e2e.js 8 passed, 0 failed
test-channels-selection-flow-e2e.js 8 passed, 0 failed
test-channels-client-state-152-e2e.js 6 passed, 0 failed
test-channel-issue-1087-e2e.js / -1111-e2e.js 3 / 2 passed, 0 failed
test-channel-fluid-e2e.js 15 passed, 0 failed
test-issue-147-packets-url-obs-e2e.js 12 passed, 0 failed
test-issue-180-packets-url-modal-e2e.js 12 passed, 0 failed
test-issue-1128-packets-layout-e2e.js 5 passed, 0 failed
test-issue-96-hide-control-e2e.js 10 passed, 0 failed
test-packet-trace-alignment-e2e.js 17 checks passed
test-e2e-playwright.js see below

test-e2e-playwright.js stops fail-fast on Version info lives on Perf dashboard, not in navbar (#navStats never fills within 10 s). This is not from this PR. It reproduces identically on a pure origin/master export built and served the same way, so it is pre-existing on master in this environment. With that one test neutralized in both trees the results are identical: 131/134 passed, 3 skipped on each. I did not diagnose the nav-stats failure further — it is outside this PR's diff, which touches no nav code. [T][A]

Extra verification

Go ↔ JS parity. The REST row gets senderPathHashSize from Go, but channels.js recomputes it in JS for WebSocket messages, so the two must agree or the badge would flip between WS and REST. I diffed them over 134,088 inputs — all 65,536 header × path-byte combinations in both the non-transport and transport layouts, plus ~3,000 random truncated/malformed frames and hand-picked edge cases — comparing packetpath.SenderHashSize against the real app.js helper lifted out of source: 0 mismatches (Go 0 ≡ JS null). The two implementations are semantically identical, including the TRACE, 0b11, direct-marker and short-input paths. [T][A]

Tests

On the merged tree against origin/master f91339f2, macOS, Go 1.26.0, Node 22 (v25.6.1).

Suite Result
npx eslint public/*.js (eslint@8, as in CI) 0 errors, 88 warnings
sh test-all.sh 220 files, 220 passed, 0 failed
node test-frontend-helpers.js 709 passed, 0 failed
node test-packets.js 143 passed, 0 failed
node test-channels-observed-path-hash-size.js 13 passed, 0 failed
cmd/server: go vet ./... + go test ./... clean; ok 48.2s
cmd/server: readonly-invariant test ok
cmd/ingestor: go vet ./... + go test ./... clean; ok 111.1s
internal/packetpath: go test -race -count=1 ./... ok 1.4s
scripts/check-xss-sinks.sh --diff origin/master rc=0

Neither known flaky test appeared: no Hash Stats sort failure and no backfill write-hold failure in any run.

Mutants

Six, all mine, each reverted from a scratch snapshot immediately after (never with git checkout).

# Mutation Result
M1 Delete "senderPathHashSize": "readonly" from .eslintrc.json Killed — eslint 0 → 3 errors at channels.js:78, packets.js:3524, packets.js:3878
M2 Summary back to senderPathHashSize(pkt.raw_hex) Killed — new E2E 3/0 → 1/2, with the reported strings; test-packets.js 143/0 → 142/1
M3 Rename senderPathHashSize in public/app.js Killed — test-channels-observed-path-hash-size.js throws instead of passing against a stub
M4 Collapse the TRACE and 0b11 branches of pathDescription Killed — marks a 0b11 width field as invalid… and does not read a hash width out of TRACE SNR bytes both fail
M5 Drop the direct-zero-hop check in Go SenderHashSize Killed — TestSenderHashSize/direct_zero_hop and /transport_direct_zero_hop fail
M6 substr(t.raw_hex, 1, 12) → 1, 4 in db.go SURVIVED the whole Go suite → finding N1. Confirmed non-equivalent: a reviewer-written transport-flood case (141122334440DEADBEEF, expect width 2) passes unmutated and reports 0 under the mutant. Temporary test deleted afterwards.

Standing checks

  • Acceptance criteria have red-before / green-after tests — yes for both P2 items (M1, M2) and for each round-2 item (M3, M4), demonstrated above rather than asserted.
  • No behaviour change beyond the purpose — the detail summary's width semantics do change in three ways, all intentional and all the point of the change: a zero-hop flood now reports its encoded width, a 0b11 field no longer claims "4 bytes", and TRACE no longer derives a width from SNR bytes. The ADVERT Advertised Hash Size row is consequently omitted for 0b11, which the round-2 report disclosed. Nothing else in the diff alters behaviour.
  • cmd/server stays read-only — no INSERT/UPDATE/DELETE/CREATE/DROP/ALTER added anywhere in cmd/server; the two changes are a widened SELECT list and a pure helper call. The readonly-invariant test passes.
  • No new map[string]interface{} outside tests — exactly one new occurrence in the whole diff, in cmd/server/channel_sender_hash_size_test.go (a test fixture assertion, explicitly exempt). Counts in db.go (66) and store.go (212) are unchanged from master.
  • No hardcoded colours — the new badge reuses the existing .ch-path-hash-badge class, whose every colour is a var(--…); no CSS was touched. CI's CSS-variable lint passed.
  • XSS gate — scripts/check-xss-sinks.sh --diff origin/master rc=0, run from an isolated clone. The new badge interpolates only a number already narrowed to 1/2/3, and a test asserts the markup-injection case renders empty.
  • Fork guards — 9 in .github/workflows/deploy.yml and 1 in release-fast-path.yml, as expected. The single workflow line added is the new E2E's registration in the Playwright job.
  • No closing keywords — none in the PR body or in any of the three commit messages.
  • Commit author — all three commits are dborup <kontakt@meshview.dk>, author and committer.

CI per job

Attempt 4 of the pipeline run for this head got a runner for the first time, so there is now a real signal for the gating job — better than the three all-cancelled attempts the round-2 report described.

Job Result
Go Build & Test success — 24 steps, real runner
Playwright E2E Tests cancelled — 0 steps, runner_name empty, ~15 min queued
Release Artifacts / Docker Image / Deploy Staging / Publish Badges skipped on the cancelled dependency

The Go job's passing steps include the ones that matter here: Run JS unit tests (test-all.sh), Preflight XSS gate — actual --diff check, Frontend lint (eslint no-undef), and Lint CSS variables. That is independent confirmation of point 1 and of several standing checks.

The Playwright job again never acquired a hosted runner — zero steps executed, so this is not a test failure and not either known flaky test. The E2E coverage in this review is therefore local only, and the Playwright job still needs a CI run once runners are available.

Not verified

  • The CI Playwright job, which has still produced no signal on this head. Covered locally instead, including the new E2E.
  • go test -race on the full cmd/server and cmd/ingestor suites. I ran those without -race; only internal/packetpath was run under the race detector.
  • The Docker image build, release artifacts, and staging deploy — all skipped in CI, and no staging or production access was used.
  • The pre-existing #navStats failure in test-e2e-playwright.js. A/B'd against origin/master to establish it is not from this PR, then set aside without diagnosis.
  • Real-world frequency of the N2 route-type mismatch. The code path is demonstrated; whether a transmission is observed with frames differing in transport-ness in practice was not measured against live data.

@dborup
dborup merged commit 34f672c into master Oct 6, 2026
26 of 30 checks passed
dborup added a commit that referenced this pull request Oct 6, 2026
…wups

fix(packets): one pktEsc listener per page and #268/#260/#212 follow-ups (#282)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants