From 0d90f208864bfe194497d2cdc29c3b9d1528d717 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 20:41:59 +0200 Subject: [PATCH 1/8] cultmesh-browser fixtures: the endpoint list and generator for the .NET chunk-URI expectations The second column is filled by the next commit, from the generator's run on .NET 10. Co-Authored-By: Claude Opus 5.5 --- .../test/fixtures/chunk-uri-dotnet.fsx | 21 +++ .../test/fixtures/chunk-uri-dotnet.tsv | 150 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.fsx create mode 100644 packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv diff --git a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.fsx b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.fsx new file mode 100644 index 00000000..aae84e5c --- /dev/null +++ b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.fsx @@ -0,0 +1,21 @@ +// Regenerates chunk-uri-dotnet.tsv: for each endpoint in its first column, the AbsoluteUri .NET 10 builds for the chunk +// request (the body of CultMeshHttpsContentTransportConnector.BuildChunkUri; recordKey "k" and the empty-content hash), +// or "THROW " where UriBuilder refuses it. The expectations in content.test.ts are this file's output and +// nothing else; never edit the second column by hand. +// docker run --rm -v "$PWD":/w -w /w mcr.microsoft.com/dotnet/sdk:10.0 dotnet fsi packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.fsx +open System +open System.IO +let hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +let path = Path.Combine(__SOURCE_DIRECTORY__, "chunk-uri-dotnet.tsv") +let rows = + File.ReadAllLines(path, Text.UTF8Encoding(false)) + |> Array.filter (fun line -> line <> "") + |> Array.map (fun line -> + let endpoint = line.Split('\t').[0] + try + let b = UriBuilder(endpoint) + let sep = if String.IsNullOrWhiteSpace(b.Query) then "" else b.Query.TrimStart('?') + "&" + b.Query <- sep + "chunkHash=" + Uri.EscapeDataString(hex) + "&recordKey=" + Uri.EscapeDataString("k") + endpoint + "\t" + b.Uri.AbsoluteUri + with e -> endpoint + "\tTHROW " + e.GetType().Name) +File.WriteAllText(path, String.Join("\n", rows) + "\n", Text.UTF8Encoding(false)) diff --git a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv new file mode 100644 index 00000000..5ac5efc1 --- /dev/null +++ b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv @@ -0,0 +1,150 @@ +https://h/x?a=%2F +https://h/x?a=%2f +https://h/a%2fb +https://h/a%2Fb +https://h/x?a=%3A +https://H/X +https://h:443/x +https://h:0443/x +https://h:/x +https://h:65535/x +https://h/./a/../b +https://h//x +https://h/x? +https://h/x??a +https://h/x?a=[b] +https://h/[x] +https://h/x;p +https://h/x@y +https://u@h/x +https://u:p@h/x +https://0x7f.1/x +https://2130706433/x +https://127.1/x +https://[::FFFF:127.0.0.1]/x +https://[0:0:0:0:0:0:0:1]/x +https://[::1]:443/x +https://h./x +https://xn--nxasmq6b.com/x +HTTPS://h/x +https:h/x +https:/h/x +https:///h/x +https://h/x?a=%e2%82%ac +https://h/x?a=%E2%82%AC +https://h/%E2%82%AC +https://h/x?a=%5e +https://h/x?a=%5E +https://h/x?a=%60 +https://h/%60 +https://h/x?a=%22 +https://h/x?a=%00 +https://h/x?a=%0A +https://h/x?a=* +https://h/x?a=(b) +https://h/x?a=$ +https://h/x?a=b?c +https://h/x# +https://h/x?# +https://h/x#f?g +https://h/x?a=b#c +https://h/x?a=b#c#d +https://h/x?a=%25 +https://h/x?a=b&&c +https://h/x?& +https://h/%41 +https://h/x?a=%3d +https://h/x?a=%26 +https://h/x?a=%2B +https://h/x?a=+ +https://h/x?a=! +https://h/x?a=,; +https://h/x/? +https://h:8443/x +https://h:8443 +https://h +https://h?a +https://h#f +https://h/x?a=%7B +https://h/x?a=%7b +https://h/x?a=:@/? +https://h/:@!$&()*+,;= +https://h/x?a=~-._ +https://h/x?a=%c3%a9 +https://h/%c3%a9 +https://h/x?a=%ff +https://h/x?a=%e2%82 +https://h/x?a=%C3 +https://h/x?a=%c3 +https://h/%ff +https://[::ffff:7f00:1]/x +https://[2001:DB8::1]/x +https://[2001:db8:0:0:0:0:0:1]/x +https://[::]/x +https://h/x?a='b +https://h/x?a={b}|^ +https://h/x?a="<>` +https://h/x?a=b c +https://h/x?a=%7e +https://h/x?a=é +https://h/{x} +https://h/a|b +https://h/a^b +https://h/a'b +https://h/x?a=b#f?g +https://h/x?#f +https://ex%41mple.com/ +https://Bücher.de/ +https://h/%7e +https://h/x?a=%zz +https://h/x?a=\b +https://h/a\b +https://h/x?a=1&& +https://h/x? +https://h/x?%3F +https://[::7f00:1]/x +https://[::127.0.0.1]/x +https://[64:ff9b::7f00:1]/x +https://[64:ff9b::127.0.0.1]/x +https://[::ffff:0:7f00:1]/x +https://[0:0:0:0:0:ffff:7f00:1]/x +https://[::ffff:1.2.3.4]/x +https://[::FFFF:1.2.3.4]/x +https://[::ffff:102:304]/x +https://[2001:db8::1.2.3.4]/x +https://[fe80::1%25eth0]/x +https://[::1]:8443/c +https://[1::]/x +https://[::2]/x +https://[0::0]/x +https://[2001:0db8:0000:0000:0000:0000:0000:0001]/x +https://[2001:db8:0:0:1:0:0:1]/x +https://[1:0:0:2:0:0:0:3]/x +https://[1:0:0:2:0:0:3:4]/x +HTTPS:/evil.example/c +https:////h/x +https:/\/h/x +https: +https:/ +https:// +https:/// +https://h:80/x +http://h:80/x +http://h:443/x +https://@h/x +https://:@h/x +https://u@h +https://h/%e2%82%ac +https://h/x?a=%f0%90%90%80 +https://h/x?a=%F0%90%90%80 +https://h/x?a=%C3%A9 +https://h/x?a=%7f +https://h/x?a=%80 +https://h/%80 +http://localhost:80/x +http://LOCALHOST/x +http://127.0.0.1:80 +http://[::1]:80/x +http://[::1]:8080/x?a=1 +https://h/x?a=%e2%82%ac%c3%a9 +https://h/x?a=%E2%82%ac From 9585586424474ae06f5435dda76ee13e4ec9729f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 20:44:43 +0200 Subject: [PATCH 2/8] cultmesh-browser fixtures: .NET 10 output for the first 150 endpoints, and the endpoints the old tables held Rows past 150 still await the generator; the next commit holds its whole output. Co-Authored-By: Claude Opus 5.5 --- .../test/fixtures/chunk-uri-dotnet.tsv | 348 ++++++++++-------- 1 file changed, 198 insertions(+), 150 deletions(-) diff --git a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv index 5ac5efc1..999dca7f 100644 --- a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv +++ b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv @@ -1,150 +1,198 @@ -https://h/x?a=%2F -https://h/x?a=%2f -https://h/a%2fb -https://h/a%2Fb -https://h/x?a=%3A -https://H/X -https://h:443/x -https://h:0443/x -https://h:/x -https://h:65535/x -https://h/./a/../b -https://h//x -https://h/x? -https://h/x??a -https://h/x?a=[b] -https://h/[x] -https://h/x;p -https://h/x@y -https://u@h/x -https://u:p@h/x -https://0x7f.1/x -https://2130706433/x -https://127.1/x -https://[::FFFF:127.0.0.1]/x -https://[0:0:0:0:0:0:0:1]/x -https://[::1]:443/x -https://h./x -https://xn--nxasmq6b.com/x -HTTPS://h/x -https:h/x -https:/h/x -https:///h/x -https://h/x?a=%e2%82%ac -https://h/x?a=%E2%82%AC -https://h/%E2%82%AC -https://h/x?a=%5e -https://h/x?a=%5E -https://h/x?a=%60 -https://h/%60 -https://h/x?a=%22 -https://h/x?a=%00 -https://h/x?a=%0A -https://h/x?a=* -https://h/x?a=(b) -https://h/x?a=$ -https://h/x?a=b?c -https://h/x# -https://h/x?# -https://h/x#f?g -https://h/x?a=b#c -https://h/x?a=b#c#d -https://h/x?a=%25 -https://h/x?a=b&&c -https://h/x?& -https://h/%41 -https://h/x?a=%3d -https://h/x?a=%26 -https://h/x?a=%2B -https://h/x?a=+ -https://h/x?a=! -https://h/x?a=,; -https://h/x/? -https://h:8443/x -https://h:8443 -https://h -https://h?a -https://h#f -https://h/x?a=%7B -https://h/x?a=%7b -https://h/x?a=:@/? -https://h/:@!$&()*+,;= -https://h/x?a=~-._ -https://h/x?a=%c3%a9 -https://h/%c3%a9 -https://h/x?a=%ff -https://h/x?a=%e2%82 -https://h/x?a=%C3 -https://h/x?a=%c3 -https://h/%ff -https://[::ffff:7f00:1]/x -https://[2001:DB8::1]/x -https://[2001:db8:0:0:0:0:0:1]/x -https://[::]/x -https://h/x?a='b -https://h/x?a={b}|^ -https://h/x?a="<>` -https://h/x?a=b c -https://h/x?a=%7e -https://h/x?a=é -https://h/{x} -https://h/a|b -https://h/a^b -https://h/a'b -https://h/x?a=b#f?g -https://h/x?#f -https://ex%41mple.com/ -https://Bücher.de/ -https://h/%7e -https://h/x?a=%zz -https://h/x?a=\b -https://h/a\b -https://h/x?a=1&& -https://h/x? -https://h/x?%3F -https://[::7f00:1]/x -https://[::127.0.0.1]/x -https://[64:ff9b::7f00:1]/x -https://[64:ff9b::127.0.0.1]/x -https://[::ffff:0:7f00:1]/x -https://[0:0:0:0:0:ffff:7f00:1]/x -https://[::ffff:1.2.3.4]/x -https://[::FFFF:1.2.3.4]/x -https://[::ffff:102:304]/x -https://[2001:db8::1.2.3.4]/x -https://[fe80::1%25eth0]/x -https://[::1]:8443/c -https://[1::]/x -https://[::2]/x -https://[0::0]/x -https://[2001:0db8:0000:0000:0000:0000:0000:0001]/x -https://[2001:db8:0:0:1:0:0:1]/x -https://[1:0:0:2:0:0:0:3]/x -https://[1:0:0:2:0:0:3:4]/x -HTTPS:/evil.example/c -https:////h/x -https:/\/h/x -https: -https:/ -https:// -https:/// -https://h:80/x -http://h:80/x -http://h:443/x -https://@h/x -https://:@h/x -https://u@h -https://h/%e2%82%ac -https://h/x?a=%f0%90%90%80 -https://h/x?a=%F0%90%90%80 -https://h/x?a=%C3%A9 -https://h/x?a=%7f -https://h/x?a=%80 -https://h/%80 -http://localhost:80/x -http://LOCALHOST/x -http://127.0.0.1:80 -http://[::1]:80/x -http://[::1]:8080/x?a=1 -https://h/x?a=%e2%82%ac%c3%a9 -https://h/x?a=%E2%82%ac +https://h/x?a=%2F https://h/x?a=%2F&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%2f https://h/x?a=%2f&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a%2fb https://h/a%2fb?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a%2Fb https://h/a%2Fb?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%3A https://h/x?a=%3A&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://H/X https://h/X?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h:443/x https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h:0443/x https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h:/x https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h:65535/x https://h:65535/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/./a/../b https://h/b?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h//x https://h//x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x? https://h/x?&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x??a https://h/x?a&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=[b] https://h/x?a=[b]&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/[x] https://h/[x]?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x;p https://h/x;p?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x@y https://h/x@y?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://u@h/x https://u@h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://u:p@h/x https://u:p@h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://0x7f.1/x https://127.0.0.1/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://2130706433/x https://127.0.0.1/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://127.1/x https://127.0.0.1/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::FFFF:127.0.0.1]/x https://[::ffff:127.0.0.1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[0:0:0:0:0:0:0:1]/x https://[::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::1]:443/x https://[::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h./x https://h./x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://xn--nxasmq6b.com/x https://xn--nxasmq6b.com/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +HTTPS://h/x https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https:h/x THROW UriFormatException +https:/h/x http://https/h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https:///h/x THROW UriFormatException +https://h/x?a=%e2%82%ac https://h/x?a=%E2%82%AC&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%E2%82%AC https://h/x?a=%E2%82%AC&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%E2%82%AC https://h/%E2%82%AC?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%5e https://h/x?a=%5e&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%5E https://h/x?a=%5E&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%60 https://h/x?a=%60&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%60 https://h/%60?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%22 https://h/x?a=%22&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%00 https://h/x?a=%00&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%0A https://h/x?a=%0A&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=* https://h/x?a=*&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=(b) https://h/x?a=(b)&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=$ https://h/x?a=$&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=b?c https://h/x?a=b?c&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x# https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k# +https://h/x?# https://h/x?&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k# +https://h/x#f?g https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#f?g +https://h/x?a=b#c https://h/x?a=b&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#c +https://h/x?a=b#c#d https://h/x?a=b&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#c#d +https://h/x?a=%25 https://h/x?a=%25&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=b&&c https://h/x?a=b&&c&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?& https://h/x?&&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%41 https://h/A?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%3d https://h/x?a=%3d&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%26 https://h/x?a=%26&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%2B https://h/x?a=%2B&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=+ https://h/x?a=+&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=! https://h/x?a=!&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=,; https://h/x?a=,;&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x/? https://h/x/?&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h:8443/x https://h:8443/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h:8443 https://h:8443/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h https://h/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h?a https://h/?a&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h#f https://h/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#f +https://h/x?a=%7B https://h/x?a=%7B&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%7b https://h/x?a=%7b&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=:@/? https://h/x?a=:@/?&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/:@!$&()*+,;= https://h/:@!$&()*+,;=?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=~-._ https://h/x?a=~-._&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%c3%a9 https://h/x?a=%C3%A9&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%c3%a9 https://h/%C3%A9?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%ff https://h/x?a=%ff&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%e2%82 https://h/x?a=%e2%82&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%C3 https://h/x?a=%C3&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%c3 https://h/x?a=%c3&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%ff https://h/%ff?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::ffff:7f00:1]/x https://[::ffff:127.0.0.1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[2001:DB8::1]/x https://[2001:db8::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[2001:db8:0:0:0:0:0:1]/x https://[2001:db8::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::]/x https://[::]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a='b https://h/x?a='b&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a={b}|^ https://h/x?a=%7Bb%7D%7C%5E&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a="<>` https://h/x?a=%22%3C%3E%60&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=b c https://h/x?a=b%20c&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%7e https://h/x?a=~&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=é https://h/x?a=%C3%A9&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/{x} https://h/%7Bx%7D?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a|b https://h/a%7Cb?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a^b https://h/a%5Eb?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a'b https://h/a'b?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=b#f?g https://h/x?a=b&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#f?g +https://h/x?#f https://h/x?&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#f +https://ex%41mple.com/ THROW UriFormatException +https://Bücher.de/ https://bücher.de/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%7e https://h/~?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%zz https://h/x?a=%25zz&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=\b https://h/x?a=%5Cb&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a\b https://h/a/b?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=1&& https://h/x?a=1&&&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x? https://h/x?&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?%3F https://h/x?%3F&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::7f00:1]/x https://[::127.0.0.1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::127.0.0.1]/x https://[::127.0.0.1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[64:ff9b::7f00:1]/x https://[64:ff9b::7f00:1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[64:ff9b::127.0.0.1]/x https://[64:ff9b::7f00:1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::ffff:0:7f00:1]/x https://[::ffff:0:127.0.0.1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[0:0:0:0:0:ffff:7f00:1]/x https://[::ffff:127.0.0.1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::ffff:1.2.3.4]/x https://[::ffff:1.2.3.4]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::FFFF:1.2.3.4]/x https://[::ffff:1.2.3.4]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::ffff:102:304]/x https://[::ffff:1.2.3.4]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[2001:db8::1.2.3.4]/x https://[2001:db8::102:304]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[fe80::1%25eth0]/x https://[fe80::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::1]:8443/c https://[::1]:8443/c?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[1::]/x https://[1::]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::2]/x https://[::2]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[0::0]/x https://[::]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[2001:0db8:0000:0000:0000:0000:0000:0001]/x https://[2001:db8::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[2001:db8:0:0:1:0:0:1]/x https://[2001:db8::1:0:0:1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[1:0:0:2:0:0:0:3]/x https://[1:0:0:2::3]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[1:0:0:2:0:0:3:4]/x https://[1::2:0:0:3:4]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +HTTPS:/evil.example/c http://https/evil.example/c?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https:////h/x THROW UriFormatException +https:/\/h/x THROW UriFormatException +https: http://https/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https:/ http://https/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https:// THROW UriFormatException +https:/// THROW UriFormatException +https://h:80/x https://h:80/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://h:80/x http://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://h:443/x http://h:443/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://@h/x https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://:@h/x https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://u@h https://u@h/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%e2%82%ac https://h/%E2%82%AC?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%f0%90%90%80 https://h/x?a=%F0%90%90%80&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%F0%90%90%80 https://h/x?a=%F0%90%90%80&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%C3%A9 https://h/x?a=%C3%A9&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%7f https://h/x?a=%7f&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%80 https://h/x?a=%80&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%80 https://h/%80?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://localhost:80/x http://localhost/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://LOCALHOST/x http://localhost/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://127.0.0.1:80 http://127.0.0.1/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://[::1]:80/x http://[::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://[::1]:8080/x?a=1 http://[::1]:8080/x?a=1&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%e2%82%ac%c3%a9 https://h/x?a=%E2%82%AC%C3%A9&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%E2%82%ac https://h/x?a=%E2%82%AC&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a;b=c,d +https://h/a!$&()*+,;=:@b +https://h/x?a!$()*+,;=:@/? +https://h/x/ +https://H:443 +https://1.2.3.4/x +https://127.1/ +https://h/x?a# +https://h/x?a=1#f +https://h/a/./b +https://h/a/../../b +https://h/%2F +https://h/a%20b +https://h/a%25b +https://[::1]/x +https://[2001:DB8::1]:8443/x +https://h/x?a=b%3Dc +https://h/-._~ +https://h/x?= +https://h/x?a&&b +http://127.0.0.1:80/x +http://127.0.0.1:8080 +https://h/%40/x +https://h/x?a=%5B%5D +https://h/x?a=%7B%7D +https://h/x?a=%2F%3A%2C +https://h/x?a=%2c%5b%7b +https://h:99999/x +https://h/x#a b +https://h/x#é +https://h/%50 +https://h/x?a=%5A +https://h/x?a=%5f +https://h/x?a=%61 +https://h/%6F +https://h/%7a +https://h/x?a=%7E +https://h/%30 +https://h/x?a=%39 +https://h/%2d +https://h/x?a=%2E +https://h/x?a=%4z +https://h/%4 +https://h/x?% +https://h/% +https://h/%g0 +https://h/x?a=%a +https://h/x?a=%%41 From 508625e20e9a51a27dd10a5dbb2afb5047744c60 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 20:48:05 +0200 Subject: [PATCH 3/8] cultmesh-browser fixtures: the whole .NET 10 output of chunk-uri-dotnet.fsx over 198 endpoints The generator's run in mcr.microsoft.com/dotnet/sdk:10.0 (UriBuilder body of BuildChunkUri); 8 rows are exceptions UriBuilder throws. Co-Authored-By: Claude Opus 5.5 --- .../test/fixtures/chunk-uri-dotnet.tsv | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv index 999dca7f..2167d10e 100644 --- a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv +++ b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv @@ -148,51 +148,51 @@ http://[::1]:80/x http://[::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae4 http://[::1]:8080/x?a=1 http://[::1]:8080/x?a=1&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k https://h/x?a=%e2%82%ac%c3%a9 https://h/x?a=%E2%82%AC%C3%A9&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k https://h/x?a=%E2%82%ac https://h/x?a=%E2%82%AC&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k -https://h/a;b=c,d -https://h/a!$&()*+,;=:@b -https://h/x?a!$()*+,;=:@/? -https://h/x/ -https://H:443 -https://1.2.3.4/x -https://127.1/ -https://h/x?a# -https://h/x?a=1#f -https://h/a/./b -https://h/a/../../b -https://h/%2F -https://h/a%20b -https://h/a%25b -https://[::1]/x -https://[2001:DB8::1]:8443/x -https://h/x?a=b%3Dc -https://h/-._~ -https://h/x?= -https://h/x?a&&b -http://127.0.0.1:80/x -http://127.0.0.1:8080 -https://h/%40/x -https://h/x?a=%5B%5D -https://h/x?a=%7B%7D -https://h/x?a=%2F%3A%2C -https://h/x?a=%2c%5b%7b -https://h:99999/x -https://h/x#a b -https://h/x#é -https://h/%50 -https://h/x?a=%5A -https://h/x?a=%5f -https://h/x?a=%61 -https://h/%6F -https://h/%7a -https://h/x?a=%7E -https://h/%30 -https://h/x?a=%39 -https://h/%2d -https://h/x?a=%2E -https://h/x?a=%4z -https://h/%4 -https://h/x?% -https://h/% -https://h/%g0 -https://h/x?a=%a -https://h/x?a=%%41 +https://h/a;b=c,d https://h/a;b=c,d?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a!$&()*+,;=:@b https://h/a!$&()*+,;=:@b?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a!$()*+,;=:@/? https://h/x?a!$()*+,;=:@/?&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x/ https://h/x/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://H:443 https://h/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://1.2.3.4/x https://1.2.3.4/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://127.1/ https://127.0.0.1/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a# https://h/x?a&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k# +https://h/x?a=1#f https://h/x?a=1&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#f +https://h/a/./b https://h/a/b?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a/../../b https://h/b?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%2F https://h/%2F?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a%20b https://h/a%20b?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/a%25b https://h/a%25b?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[::1]/x https://[::1]/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://[2001:DB8::1]:8443/x https://[2001:db8::1]:8443/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=b%3Dc https://h/x?a=b%3Dc&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/-._~ https://h/-._~?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?= https://h/x?=&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a&&b https://h/x?a&&b&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://127.0.0.1:80/x http://127.0.0.1/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +http://127.0.0.1:8080 http://127.0.0.1:8080/?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%40/x https://h/%40/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%5B%5D https://h/x?a=%5B%5D&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%7B%7D https://h/x?a=%7B%7D&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%2F%3A%2C https://h/x?a=%2F%3A%2C&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%2c%5b%7b https://h/x?a=%2c%5b%7b&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h:99999/x THROW UriFormatException +https://h/x#a b https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#a%20b +https://h/x#é https://h/x?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k#%C3%A9 +https://h/%50 https://h/P?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%5A https://h/x?a=Z&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%5f https://h/x?a=_&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%61 https://h/x?a=a&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%6F https://h/o?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%7a https://h/z?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%7E https://h/x?a=~&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%30 https://h/0?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%39 https://h/x?a=9&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%2d https://h/-?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%2E https://h/x?a=.&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%4z https://h/x?a=%254z&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%4 https://h/%254?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?% https://h/x?%25&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/% https://h/%25?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/%g0 https://h/%25g0?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%a https://h/x?a=%25a&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https://h/x?a=%%41 https://h/x?a=%25A&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k From 1082b55f51607d70a483d9d9042a6060aa090226 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 20:48:06 +0200 Subject: [PATCH 4/8] cultnet-ts: validateManifest refuses sizes the reference's types cannot hold Manifest total and chunk sizes must be whole numbers below 2^53, and a chunk size must fit an int (2147483647), for hand-built values as well as decoded ones. Messages for the whole-number rule now name the bound. No C#-written vector changes. Co-Authored-By: Claude Opus 5.5 --- packages/cultnet-ts/src/content.ts | 13 +++++++++---- packages/cultnet-ts/test/content.test.ts | 23 +++++++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/cultnet-ts/src/content.ts b/packages/cultnet-ts/src/content.ts index 27b4379f..98c86704 100644 --- a/packages/cultnet-ts/src/content.ts +++ b/packages/cultnet-ts/src/content.ts @@ -97,18 +97,23 @@ export async function artifactRecordKey(artifactId: string | null, version: stri // ---- validation ------------------------------------------------------------------------------------------------------ +const INT32_MAX = 2 ** 31 - 1; + /** * `ValidateManifestShape`, in its order and with its messages, then the metadata keys a dictionary could not hold twice. - * C# sizes are `long`, so a fraction, NaN or Infinity cannot exist on the wire; a hand-built manifest is refused here, once. + * Sizes are the reference's types: the manifest total and every offset a `long` (held here below 2^53, the largest integer a + * number keeps exact) and a chunk size an `int`. A fraction, NaN, Infinity or a size past those bounds cannot exist on the wire + * and is refused here, once, whether it was decoded or hand-built. */ export function validateManifest(manifest: CultMeshCdnArtifactManifest): void { if (manifest.sizeBytes < 0) throw new Error("CDN artifact manifest has a negative size."); - if (!Number.isInteger(manifest.sizeBytes)) throw new Error("CDN artifact manifest size is not a whole number."); + if (!Number.isSafeInteger(manifest.sizeBytes)) throw new Error("CDN artifact manifest size is not a whole number below 2^53."); let expectedOffset = 0; for (const reference of [...manifest.chunks].sort((a, b) => a.offset - b.offset)) { if (reference.offset !== expectedOffset) throw new Error("CDN artifact chunks are not contiguous."); if (reference.sizeBytes < 0) throw new Error("CDN artifact chunk has a negative size."); - if (!Number.isInteger(reference.sizeBytes)) throw new Error("CDN artifact chunk size is not a whole number."); + if (!Number.isSafeInteger(reference.sizeBytes)) throw new Error("CDN artifact chunk size is not a whole number below 2^53."); + if (reference.sizeBytes > INT32_MAX) throw new Error("CDN artifact chunk size is larger than 2147483647."); expectedOffset += reference.sizeBytes; } if (expectedOffset !== manifest.sizeBytes) throw new Error("CDN artifact chunk sizes do not sum to the manifest size."); @@ -286,7 +291,7 @@ export async function packContent( ): Promise<{ manifest: CultMeshCdnArtifactManifest; chunks: CultMeshCdnPackedChunk[] }> { if (isNullOrWhiteSpaceCSharp(artifactId)) throw new Error("Artifact id must be non-empty. (Parameter 'artifactId')"); const size = options.chunkSizeBytes ?? DEFAULT_CHUNK_SIZE_BYTES; - if (!Number.isInteger(size) || size < 1 || size > 2 ** 31 - 1) throw new Error("ChunkSizeBytes must be between 1 and 2147483647."); + if (!Number.isInteger(size) || size < 1 || size > INT32_MAX) throw new Error("ChunkSizeBytes must be between 1 and 2147483647."); const offsets = bytes.length === 0 ? [0] : Array.from({ length: Math.ceil(bytes.length / size) }, (_, index) => index * size); const chunks = await Promise.all(offsets.map(async offset => { const payload = bytes.slice(offset, offset + size); diff --git a/packages/cultnet-ts/test/content.test.ts b/packages/cultnet-ts/test/content.test.ts index 6d30b7d2..3ff58d48 100644 --- a/packages/cultnet-ts/test/content.test.ts +++ b/packages/cultnet-ts/test/content.test.ts @@ -231,12 +231,12 @@ test("validateManifest refuses negative size, a gap, an overlap and a size-sum m validateManifest(manifestOf({ sizeBytes: 0, chunks: [] })); }); -test("validateManifest refuses a size that is not a whole number: a fraction, NaN or Infinity, in the manifest or in a chunk", () => { +test("validateManifest refuses a size that is not a whole number below 2^53 and a chunk size an int cannot hold, whether decoded or hand-built", () => { const chunk = (offset: number, sizeBytes: number) => ({ chunkHash: HASH, offset, sizeBytes, recordKey: "k" }); const refuses = (over: Partial, message: string) => assert.throws(() => validateManifest(manifestOf(over)), new Error(message)); - const manifestMessage = "CDN artifact manifest size is not a whole number."; - const chunkMessage = "CDN artifact chunk size is not a whole number."; + const manifestMessage = "CDN artifact manifest size is not a whole number below 2^53."; + const chunkMessage = "CDN artifact chunk size is not a whole number below 2^53."; for (const size of [1.5, Number.NaN, Number.POSITIVE_INFINITY]) refuses({ sizeBytes: size, chunks: [] }, manifestMessage); // fractional chunk sizes that still tile the manifest are contiguous, so only the whole-number rule refuses them refuses({ sizeBytes: 6, chunks: [chunk(0, 4), chunk(4, 1.5), chunk(5.5, 0.5)] }, chunkMessage); @@ -245,7 +245,22 @@ test("validateManifest refuses a size that is not a whole number: a fraction, Na refuses({ sizeBytes: Number.POSITIVE_INFINITY, chunks: [chunk(0, Number.POSITIVE_INFINITY)] }, manifestMessage); refuses({ sizeBytes: 4, chunks: [chunk(0, 2), chunk(2, Number.POSITIVE_INFINITY)] }, chunkMessage); refuses({ sizeBytes: 0, chunks: [chunk(0, -1)] }, "CDN artifact chunk has a negative size."); - validateManifest(manifestOf({ sizeBytes: 2 ** 53 - 1, chunks: [chunk(0, 2 ** 53 - 1)] })); + // C# holds the manifest total and offsets in a long, a chunk size in an int; a number holds integers exactly only below 2^53 + const int32Max = 2 ** 31 - 1; + const tooBig = "CDN artifact chunk size is larger than 2147483647."; + refuses({ sizeBytes: 2 ** 31, chunks: [chunk(0, 2 ** 31)] }, tooBig); + refuses({ sizeBytes: 2 ** 31 + 5, chunks: [chunk(0, 5), chunk(5, 2 ** 31)] }, tooBig); + refuses({ sizeBytes: 2 ** 53 - 1, chunks: [chunk(0, 2 ** 53 - 1)] }, tooBig); + for (const size of [2 ** 53, 2 ** 63, 2 ** 64]) { + refuses({ sizeBytes: size, chunks: [] }, manifestMessage); + refuses({ sizeBytes: size, chunks: [chunk(0, size)] }, manifestMessage); + refuses({ sizeBytes: 4, chunks: [chunk(0, size)] }, chunkMessage); + } + // the sum can only pass 2^53 by exceeding the manifest total, which is below it; an offset past 2^53 matches no sum + refuses({ sizeBytes: 2 ** 53, chunks: [chunk(0, int32Max), chunk(int32Max, 2 ** 53 - int32Max)] }, manifestMessage); + refuses({ sizeBytes: 1, chunks: [chunk(2 ** 53, 1)] }, "CDN artifact chunks are not contiguous."); + validateManifest(manifestOf({ sizeBytes: 2 ** 31 + 2, chunks: [chunk(0, 1), chunk(1, int32Max), chunk(2 ** 31, 2)] })); + validateManifest(manifestOf({ sizeBytes: int32Max, chunks: [chunk(0, int32Max)] })); }); test("normalizeHash: 'SHA256:ABC ' -> 'abc', ' sha256:abc' -> 'sha256:abc', blank throws", () => { From cdeda66a90e5d05a2530bd856104e4e21d3bc92e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 20:48:07 +0200 Subject: [PATCH 5/8] cultmesh-browser: URL parity from the .NET fixture, option-independent hash tests, rejection timed where it is decided The reader refuses a scheme without an authority form, escapes of bytes past ASCII and IPv6 literals that embed an IPv4 address; parity is tested against chunk-uri-dotnet.tsv. Wrong-bytes tests run at maxBytes equal to the size and at concurrency 1 and 32. The stall test measures settlement against a 100 ms timer and counts requests started after the failure. Co-Authored-By: Claude Opus 5.5 --- packages/cultmesh-browser/src/content.ts | 12 +- .../cultmesh-browser/test/content.test.ts | 172 +++++++----------- 2 files changed, 76 insertions(+), 108 deletions(-) diff --git a/packages/cultmesh-browser/src/content.ts b/packages/cultmesh-browser/src/content.ts index c5f95a04..d9e09b4f 100644 --- a/packages/cultmesh-browser/src/content.ts +++ b/packages/cultmesh-browser/src/content.ts @@ -50,11 +50,15 @@ const escapeData = (value: string): string => encodeURIComponent(value).replace( // An endpoint the request URL of which is the same string in .NET `UriBuilder` and WHATWG `URL`. Both are free to rewrite what // they dislike (`'` in a query, `{}|^\` and non-ASCII anywhere, a percent sign that is not an escape, an escaped unreserved -// character, an IDN host), and they rewrite it differently, which would split CDN cache entries between runtimes; so those -// spellings are refused instead of translated. +// character, an escaped byte past ASCII, an IDN host, an IPv6 literal that embeds an IPv4 address), and they rewrite it +// differently, which would split CDN cache entries between runtimes; so those spellings are refused instead of translated. +// A scheme not followed by exactly `//` and a host is no authority form: the C# connector refuses it, and `URL` repairs it. +const AUTHORITY_FORM = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/]/; const PLAIN_ENDPOINT = /^(?:[A-Za-z0-9\-._~:/?#@!$&*()+,;=[\]]|%[0-9A-Fa-f]{2})*$/; -const ESCAPED_UNRESERVED = /%(?:2[de]|3\d|4[1-9a-f]|5[0-9af]|6[1-9a-f]|7[0-9ae])/i; -const isPlainEndpoint = (endpoint: string): boolean => PLAIN_ENDPOINT.test(endpoint) && !ESCAPED_UNRESERVED.test(endpoint); +const ESCAPE_DIVERGES = /%(?:2[de]|3\d|4[1-9a-f]|5[0-9af]|6[1-9a-f]|7[0-9ae]|[89a-f][0-9a-f])/i; +const EMBEDS_IPV4 = /^\[::(?:ffff(?::0)?:)?[0-9a-f]{1,4}:[0-9a-f]{1,4}\]$/; +const isPlainEndpoint = (endpoint: string): boolean => + AUTHORITY_FORM.test(endpoint) && PLAIN_ENDPOINT.test(endpoint) && !ESCAPE_DIVERGES.test(endpoint) && !EMBEDS_IPV4.test(new URL(endpoint).hostname); /** `CultMeshHttpsContentTransportConnector.BuildChunkUri`: an existing query (even an empty one, leading `?`s trimmed) is kept and joined with `&`. */ function chunkUrl(endpoint: string, hash: string, recordKey: string): string { diff --git a/packages/cultmesh-browser/test/content.test.ts b/packages/cultmesh-browser/test/content.test.ts index 5af89c10..8c6767db 100644 --- a/packages/cultmesh-browser/test/content.test.ts +++ b/packages/cultmesh-browser/test/content.test.ts @@ -6,6 +6,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; import type { AddressInfo } from "node:net"; +import { readFileSync } from "node:fs"; import { gzipSync } from "node:zlib"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -100,108 +101,51 @@ test("request URLs equal the .NET BuildChunkUri AbsoluteUri for every probed end assert.equal(absent.calls[0]!.url, `${ENDPOINT}?chunkHash=${hex}&recordKey=`); }); -// Rows probed with .NET 10 UriBuilder (the BuildChunkUri body) at the same endpoints: each accepted endpoint yields exactly the -// AbsoluteUri .NET yields ( stands for the chunkHash and recordKey query tail); each refused endpoint is a spelling on which -// .NET and WHATWG URL disagree (or .NET throws), so the reader refuses it rather than request a different URL. -const PARITY_ACCEPTED: [string, string][] = [ - ["https://h/a%2fb", "https://h/a%2fb?"], - ["https://h/./a/../b", "https://h/b?"], - ["https://h/x?a=b#f?g", "https://h/x?a=b&#f?g"], - ["https://h/x?#f", "https://h/x?&#f"], - ["https://h:0443/x", "https://h/x?"], - ["https://h./x", "https://h./x?"], - ["https://h/x?a=[b]", "https://h/x?a=[b]&"], - ["https://u:p@h/x", "https://u:p@h/x?"], - ["https://h/x?a=1&&", "https://h/x?a=1&&&"], - ["https://h/x?%3F", "https://h/x?%3F&"], - ["https://h:/x", "https://h/x?"], - ["https://h/x?a=b?c", "https://h/x?a=b?c&"], - ["https://h/a;b=c,d", "https://h/a;b=c,d?"], - ["https://h/a!$&()*+,;=:@b", "https://h/a!$&()*+,;=:@b?"], - ["https://h/x?a!$()*+,;=:@/?", "https://h/x?a!$()*+,;=:@/?&"], - ["https://h//x", "https://h//x?"], - ["https://h/x/", "https://h/x/?"], - ["https://H:443", "https://h/?"], - ["https://1.2.3.4/x", "https://1.2.3.4/x?"], - ["https://0x7f.1/x", "https://127.0.0.1/x?"], - ["https://127.1/", "https://127.0.0.1/?"], - ["https://h/x#", "https://h/x?#"], - ["https://h/x?a#", "https://h/x?a&#"], - ["https://h/x?a=1#f", "https://h/x?a=1&#f"], - ["https://h/a/./b", "https://h/a/b?"], - ["https://h/a/../../b", "https://h/b?"], - ["https://h/%2F", "https://h/%2F?"], - ["https://h/a%20b", "https://h/a%20b?"], - ["https://h/a%25b", "https://h/a%25b?"], - ["https://h/x?a=%25", "https://h/x?a=%25&"], - ["https://h/x?a=%2f", "https://h/x?a=%2f&"], - ["https://[::1]/x", "https://[::1]/x?"], - ["https://[2001:DB8::1]:8443/x", "https://[2001:db8::1]:8443/x?"], - ["https://h/x?a=b%3Dc", "https://h/x?a=b%3Dc&"], - ["https://h/-._~", "https://h/-._~?"], - ["https://h/x?=", "https://h/x?=&"], - ["https://h/x?&", "https://h/x?&&"], - ["https://h/x?a&&b", "https://h/x?a&&b&"], - ["http://127.0.0.1:80/x", "http://127.0.0.1/x?"], - ["http://127.0.0.1:8080", "http://127.0.0.1:8080/?"], - ["https://h/%40/x", "https://h/%40/x?"], - ["https://h/x?a=%5B%5D", "https://h/x?a=%5B%5D&"], - ["https://h/%60", "https://h/%60?"], - ["https://h/x?a=%7B%7D", "https://h/x?a=%7B%7D&"], - ["https://h/x?a=%2F%3A%2C", "https://h/x?a=%2F%3A%2C&"], - ["https://h/x?a=%2c%5b%7b", "https://h/x?a=%2c%5b%7b&"], -]; -const PARITY_REFUSED: string[] = [ - "https://h/x?a='b", - "https://h/x?a={b}|^", - "https://h/x?a=\"<>`", - "https://h/x?a=b c", - "https://h/x?a=%7e", - "https://h/x?a=é", - "https://h/{x}", - "https://h/a|b", - "https://h/a^b", - "https://h/a'b", - "https://ex%41mple.com/", - "https://Bücher.de/", - "https://h/%7e", - "https://h/x?a=%zz", - "https://h/x?a=\\b", - "https://h/a\\b", - "https://h/x? ", - "https://h:99999/x", - "https://h/%41", - "https://h/x#a b", - "https://h/x#é", - "https://h/%50", - "https://h/x?a=%5A", - "https://h/x?a=%5f", - "https://h/x?a=%61", - "https://h/%6F", - "https://h/%7a", - "https://h/x?a=%7E", - "https://h/%30", - "https://h/x?a=%39", - "https://h/%2d", - "https://h/x?a=%2E", - "https://h/x?a=%4z", - "https://h/%4", - "https://h/x?%", -]; +// test/fixtures/chunk-uri-dotnet.tsv holds, for each endpoint, the AbsoluteUri .NET 10 builds (or the exception UriBuilder throws); +// chunk-uri-dotnet.fsx produced it, and nothing in this file spells an expected URL by hand. The reader may refuse an endpoint +// on which the two runtimes could disagree, but one it accepts must request exactly the URL .NET does, and one .NET refuses it +// must refuse too. +const FIXTURE_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; +const DOTNET_ROWS = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "test", "fixtures", "chunk-uri-dotnet.tsv"), "utf8") + .split(/\r?\n/).filter(line => line !== "").map(line => { const [endpoint, uri] = line.split("\t"); return { endpoint: endpoint!, uri: uri! }; }); -test("request URLs equal .NET UriBuilder's for every plain endpoint spelling, and endpoints on which the two disagree are refused", async () => { +test("request URLs equal what .NET builds for every endpoint the reader accepts, and endpoints .NET refuses are refused", async () => { const { manifest, chunks } = await pack(1, 4); const hex = manifest.chunks[0]!.chunkHash!; - const tail = `chunkHash=${hex}&recordKey=k`; - for (const [endpoint, expected] of PARITY_ACCEPTED) { - const server = chunkServer(chunks); - await fetchVerifiedContent(endpoint, withChunk(manifest, 0, { recordKey: "k" }), { fetch: server.fetch }); - assert.equal(server.calls[0]!.url, expected.replace("", tail), endpoint); + const requested = (uri: string) => uri.replace(`chunkHash=${FIXTURE_HASH}`, `chunkHash=${hex}`); + let accepted = 0; + for (const { endpoint, uri } of DOTNET_ROWS) { + const viaContent = chunkServer(chunks); + const viaChunk = chunkServer(chunks); + const content = await fetchVerifiedContent(endpoint, withChunk(manifest, 0, { recordKey: "k" }), { fetch: viaContent.fetch }).then(() => null, (error: unknown) => error); + const chunk = await fetchVerifiedChunk(endpoint, hex, 1, { fetch: viaChunk.fetch }).then(() => null, (error: unknown) => error); + if (content === null && chunk === null) { + accepted++; + assert.notEqual(viaContent.calls.length, 0, endpoint); + assert.ok(!uri.startsWith("THROW"), `${endpoint} is refused by .NET but requested`); + assert.equal(viaContent.calls[0]!.url, requested(uri), endpoint); + assert.equal(viaChunk.calls[0]!.url, requested(uri).replace("recordKey=k", `recordKey=${encodeURIComponent(`mesh:cdn:chunk:${hex}`)}`), endpoint); + } else { + assert.ok(codeOf("insecure-endpoint")(content) && codeOf("insecure-endpoint")(chunk), `${endpoint} is refused for another reason`); + assert.equal(viaContent.calls.length + viaChunk.calls.length, 0, endpoint); + } } - for (const endpoint of [...PARITY_REFUSED, "https://h/%", "https://h/%g0", "https://h/x?a=%a", "https://h/x?a=%%41"]) { + assert.ok(accepted >= 90, `only ${accepted} fixture endpoints are accepted`); +}); + +test("the spellings on which the reader once differed from .NET are rows of the fixture and are refused", async () => { + const { manifest, chunks } = await pack(1, 4); + const rows = new Map(DOTNET_ROWS.map(row => [row.endpoint, row.uri])); + const refused = [ + "https:h/x", "https:/h/x", "https:///h/x", "HTTPS:/evil.example/c", "https:////h/x", "https://[::FFFF:127.0.0.1]/x", "https://[::ffff:7f00:1]/x", + "https://[::7f00:1]/x", "https://[::ffff:0:7f00:1]/x", "https://[0:0:0:0:0:ffff:7f00:1]/x", "https://h/x?a=%e2%82%ac", "https://h/x?a=%c3%a9", "https://h/%c3%a9", + "https://h/%80", "https://h/x?a=%C3%A9", + ]; + for (const endpoint of refused) { + assert.ok(rows.has(endpoint), `${endpoint} is not a fixture row`); const server = chunkServer(chunks); await assert.rejects(fetchVerifiedContent(endpoint, manifest, { fetch: server.fetch }), codeOf("insecure-endpoint"), endpoint); - await assert.rejects(fetchVerifiedChunk(endpoint, hex, 1, { fetch: server.fetch }), codeOf("insecure-endpoint"), endpoint); + await assert.rejects(fetchVerifiedChunk(endpoint, manifest.chunks[0]!.chunkHash!, 1, { fetch: server.fetch }), codeOf("insecure-endpoint"), endpoint); assert.equal(server.calls.length, 0, endpoint); } }); @@ -527,8 +471,12 @@ test("wrong bytes of the right length are refused at every size, whichever byte const hash = manifest.chunks[0]!.chunkHash!; for (const at of [0, size >> 1, size - 1]) { const wrong = double(() => new Response(flipped(chunks[0]!.payload, at) as BodyInit)); - await assert.rejects(fetchVerifiedChunk(ENDPOINT, hash, size, { fetch: wrong.fetch }), codeOf("chunk-hash-mismatch", { chunkHash: hash }), `chunk ${size} byte ${at}`); - await assert.rejects(fetchVerifiedContent(ENDPOINT, manifest, { fetch: wrong.fetch }), codeOf("chunk-hash-mismatch", { chunkHash: hash }), `content ${size} byte ${at}`); + // no size may sit past a threshold that follows from an option: the cap equal to the size, and the least and most concurrency, are tried at every size + for (const options of [{}, { maxBytes: size }, { maxConcurrentChunkRequests: 1 }, { maxConcurrentChunkRequests: 32 }, { maxBytes: size, maxConcurrentChunkRequests: 1 }]) { + const label = `${size} byte ${at} ${JSON.stringify(options)}`; + await assert.rejects(fetchVerifiedChunk(ENDPOINT, hash, size, { fetch: wrong.fetch, ...options }), codeOf("chunk-hash-mismatch", { chunkHash: hash }), `chunk ${label}`); + await assert.rejects(fetchVerifiedContent(ENDPOINT, manifest, { fetch: wrong.fetch, ...options }), codeOf("chunk-hash-mismatch", { chunkHash: hash }), `content ${label}`); + } } } const { manifest, chunks } = await pack(4500, 1500); @@ -554,23 +502,39 @@ test("a body of the wrong length in one chunk of several is length-mismatch nami } }); -test("a failure is thrown without waiting for a fetch whose body ignores the abort, and so is a caller abort", async () => { - const { manifest } = await pack(30, 10); +/** Whether `promise` settles before a timer started now fires: rejection is judged where it is decided, not against a generous test timeout. */ +const settlesWithin = async (promise: Promise, ms: number): Promise => { + let timer: NodeJS.Timeout; + const late = new Promise(resolve => { timer = setTimeout(() => resolve(false), ms); }); + return Promise.race([promise.then(() => true, () => true), late]).finally(() => clearTimeout(timer)); +}; + +test("a failure is thrown at once, without waiting for a fetch whose body ignores the abort, and so is a caller abort; nothing is requested after either", async () => { + const { manifest } = await pack(100, 10); const stalled = () => new Response(new ReadableStream({ start(controller) { controller.enqueue(body(3)); } })); const firstWrong = double((_, index) => index === 0 ? new Response(new Uint8Array(10)) : stalled()); - await assert.rejects(within(fetchVerifiedContent(ENDPOINT, manifest, { fetch: firstWrong.fetch, maxConcurrentChunkRequests: 3 })), codeOf("chunk-hash-mismatch")); + const failing = fetchVerifiedContent(ENDPOINT, manifest, { fetch: firstWrong.fetch, maxConcurrentChunkRequests: 3 }); + assert.ok(await settlesWithin(failing, 100), "the first failure waited"); + await assert.rejects(failing, codeOf("chunk-hash-mismatch")); + await new Promise(resolve => setTimeout(resolve, 30)); + assert.equal(firstWrong.calls.length, 3, "a request started after the first failure"); const reason = new Error("stop waiting"); const never = double(() => new Promise(() => {})); const early = new AbortController(); const noResponse = fetchVerifiedContent(ENDPOINT, manifest, { fetch: never.fetch, signal: early.signal }); early.abort(reason); - await assert.rejects(within(noResponse), (error: unknown) => error === reason); + assert.ok(await settlesWithin(noResponse, 100), "the caller's abort waited"); + await assert.rejects(noResponse, (error: unknown) => error === reason); const stalledBodies = double(stalled); const late = new AbortController(); const waiting = fetchVerifiedContent(ENDPOINT, manifest, { fetch: stalledBodies.fetch, signal: late.signal }); - for (let waited = 0; stalledBodies.calls.length < 3 && waited < 2000; waited++) await new Promise(resolve => setTimeout(resolve, 1)); + for (let waited = 0; stalledBodies.calls.length < 4 && waited < 2000; waited++) await new Promise(resolve => setTimeout(resolve, 1)); + assert.equal(stalledBodies.calls.length, 4); late.abort(reason); - await assert.rejects(within(waiting), (error: unknown) => error === reason); + assert.ok(await settlesWithin(waiting, 100), "the caller's abort waited for the stalled bodies"); + await assert.rejects(waiting, (error: unknown) => error === reason); + await new Promise(resolve => setTimeout(resolve, 30)); + assert.equal(stalledBodies.calls.length, 4, "a request started after the caller's abort"); }); test("an abort that lands while the last bytes are hashed rejects with its reason: no success for a cancelled caller", async () => { From f0be628be6ba03688b97d3f56cbb5f154c85e789 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 20:50:04 +0200 Subject: [PATCH 6/8] cultmesh-browser: the unallocatable-size test uses chunks an int can hold The owner now refuses a chunk past 2147483647, so the oversize manifest is 32768 chunks of that size (about 64 TiB) instead of one 2^53 - 1 chunk. Co-Authored-By: Claude Opus 5.5 --- packages/cultmesh-browser/test/content.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cultmesh-browser/test/content.test.ts b/packages/cultmesh-browser/test/content.test.ts index 8c6767db..cc0bb322 100644 --- a/packages/cultmesh-browser/test/content.test.ts +++ b/packages/cultmesh-browser/test/content.test.ts @@ -562,7 +562,10 @@ test("an abort that lands while the last bytes are hashed rejects with its reaso test("a size the runtime cannot allocate is too-large in both exports, not a RangeError, and nothing is requested", async () => { const { manifest } = await pack(10, 10); const size = Number.MAX_SAFE_INTEGER; - const huge = { ...manifest, sizeBytes: size, chunks: [{ ...manifest.chunks[0]!, offset: 0, sizeBytes: size }] }; + // chunks are ints, so a manifest this large is many of them: 32768 of 2147483647 bytes is about 64 TiB + const int32Max = 2 ** 31 - 1; + const count = 2 ** 15; + const huge = { ...manifest, sizeBytes: count * int32Max, chunks: Array.from({ length: count }, (_, index) => ({ ...manifest.chunks[0]!, offset: index * int32Max, sizeBytes: int32Max })) }; const server = double(() => { throw new Error("no request expected"); }); await assert.rejects(fetchVerifiedContent(ENDPOINT, huge, { fetch: server.fetch, maxBytes: size }), codeOf("too-large")); await assert.rejects(fetchVerifiedChunk(ENDPOINT, manifest.chunks[0]!.chunkHash!, size, { fetch: server.fetch, maxBytes: size }), codeOf("too-large")); From 0d7f9a8433053a6583273ebc2b89dd8bb938c9ae Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 21:06:01 +0200 Subject: [PATCH 7/8] cultmesh-browser fixtures: three endpoints whose query holds a scheme-like text, for the generator These pin that the authority-form check is anchored at the start of the endpoint; their .NET output follows in the next commit. Co-Authored-By: Claude Opus 5.5 --- packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv index 2167d10e..288568c3 100644 --- a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv +++ b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv @@ -196,3 +196,6 @@ https://h/% https://h/%25?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b https://h/%g0 https://h/%25g0?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k https://h/x?a=%a https://h/x?a=%25a&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k https://h/x?a=%%41 https://h/x?a=%25A&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https:h/x?a=b://c +https:/h/x?u=http://z +https:h/x#a://b From 7edaf08c175222a66c0ed30374841e1a7600367c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 21:06:22 +0200 Subject: [PATCH 8/8] cultmesh-browser fixtures: .NET 10 output for the 201 endpoints Generator run in mcr.microsoft.com/dotnet/sdk:10.0; the three added rows are exceptions UriBuilder throws. Co-Authored-By: Claude Opus 5.5 --- .../cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv index 288568c3..26e1663d 100644 --- a/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv +++ b/packages/cultmesh-browser/test/fixtures/chunk-uri-dotnet.tsv @@ -196,6 +196,6 @@ https://h/% https://h/%25?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b https://h/%g0 https://h/%25g0?chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k https://h/x?a=%a https://h/x?a=%25a&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k https://h/x?a=%%41 https://h/x?a=%25A&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k -https:h/x?a=b://c -https:/h/x?u=http://z -https:h/x#a://b +https:h/x?a=b://c THROW UriFormatException +https:/h/x?u=http://z http://https/h/x?u=http://z&chunkHash=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&recordKey=k +https:h/x#a://b THROW UriFormatException