- commit
- 588159a
- parent
- 7c27c34
- author
- Eric Bower
- date
- 2026-02-26 21:36:17 -0500 EST
refactor(pgs): custom rfc9111 compliant cache
12 files changed,
+1913,
-1321
M
Makefile
+5,
-1
1@@ -85,12 +85,16 @@ build-pico:
2 go build -o "build/pico-ssh" "./cmd/pico/ssh"
3 .PHONY: build-auth
4
5+build-pgs-cdn:
6+ go build -o "build/pgs-cdn" "./cmd/pgs/cdn"
7+.PHONY: build-cdn
8+
9 build-%:
10 go build -o "build/$*-web" "./cmd/$*/web"
11 go build -o "build/$*-ssh" "./cmd/$*/ssh"
12 .PHONY: build-%
13
14-build: build-prose build-pastes build-feeds build-pgs build-auth build-pico build-pipe
15+build: build-prose build-pastes build-feeds build-pgs build-pgs-cdn build-auth build-pico build-pipe
16 .PHONY: build
17
18 scripts:
+58,
-49
1@@ -3,22 +3,22 @@ package main
2 import (
3 "context"
4 "fmt"
5+ "io"
6 "log/slog"
7 "net"
8 "net/http"
9- "net/http/httputil"
10 "net/url"
11 "strings"
12
13- "github.com/darkweak/souin/pkg/middleware"
14- "github.com/hashicorp/golang-lru/v2/expirable"
15 "github.com/picosh/pico/pkg/apps/pgs"
16+ "github.com/picosh/pico/pkg/httpcache"
17 "github.com/picosh/pico/pkg/shared"
18 "github.com/prometheus/client_golang/prometheus/promhttp"
19 )
20
21 func main() {
22- withPipe := strings.ToLower(shared.GetEnv("PICO_PIPE_ENABLED", "true")) == "true"
23+ pipeEnabled := shared.GetEnv("PICO_PIPE_ENABLED", "true")
24+ withPipe := strings.ToLower(pipeEnabled) == "true"
25 logger := shared.CreateLogger("pgs-cdn", withPipe)
26 ctx := context.Background()
27 drain := pgs.CreateSubCacheDrain(ctx, logger)
28@@ -27,19 +27,14 @@ func main() {
29 _ = pubsub.Close()
30 }()
31 cfg := pgs.NewPgsConfig(logger, nil, nil, drain)
32- httpCache := pgs.SetupCache(cfg)
33- router := &pgs.WebRouter{
34- Cfg: cfg,
35- RedirectsCache: expirable.NewLRU[string, []*pgs.RedirectRule](2048, nil, shared.CacheTimeout),
36- HeadersCache: expirable.NewLRU[string, []*pgs.HeaderRule](2048, nil, shared.CacheTimeout),
37- }
38+ proxy := newProxyServe(cfg.Logger)
39+ httpCache := pgs.NewPgsHttpCache(cfg, proxy)
40 cacher := &cachedHttp{
41- handler: httpCache,
42- routes: router,
43+ Logger: cfg.Logger,
44+ Cache: httpCache,
45 }
46
47- go router.WatchCacheClear()
48- go router.CacheMgmt(ctx, httpCache, cfg.CacheClearingQueue)
49+ go pgs.CacheMgmt(ctx, cfg.CacheClearingQueue, cfg, httpCache.Cache)
50
51 portStr := fmt.Sprintf(":%s", cfg.WebPort)
52 cfg.Logger.Info(
53@@ -51,29 +46,54 @@ func main() {
54 cfg.Logger.Error("listen and serve", "err", err)
55 }
56
57-type cachedHttp struct {
58- handler *middleware.SouinBaseHandler
59- routes *pgs.WebRouter
60+type proxyServe struct {
61+ Logger *slog.Logger
62+ transport *http.Transport
63 }
64
65-type CustomTransport struct {
66- *http.Transport
67- Logger *slog.Logger
68+func newProxyServe(logger *slog.Logger) *proxyServe {
69+ defaultTransport := http.DefaultTransport.(*http.Transport)
70+ oldDial := defaultTransport.DialContext
71+ transport := &http.Transport{
72+ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
73+ return oldDial(ctx, "tcp", "ash.pgs.sh:443")
74+ },
75+ }
76+ return &proxyServe{Logger: logger, transport: transport}
77 }
78
79-func (t *CustomTransport) RoundTrip(request *http.Request) (*http.Response, error) {
80- // reqDump, _ := httputil.DumpRequestOut(request, false)
81- // t.Logger.Info("request", "dump", string(reqDump))
82- response, err := http.DefaultTransport.RoundTrip(request)
83+func (p *proxyServe) ServeHTTP(w http.ResponseWriter, req *http.Request) {
84+ target, _ := url.Parse(partialURL(req))
85+ p.Logger.Info("proxying request to ash.pgs.sh", "url", target.String())
86+
87+ proxyReq := req.Clone(req.Context())
88+ proxyReq.URL.Scheme = target.Scheme
89+ proxyReq.URL.Host = target.Host
90+ proxyReq.URL.Path = target.Path
91+ proxyReq.URL.RawQuery = target.RawQuery
92+ proxyReq.RequestURI = ""
93+
94+ resp, err := p.transport.RoundTrip(proxyReq)
95+ if err != nil {
96+ http.Error(w, err.Error(), http.StatusBadGateway)
97+ return
98+ }
99+ defer func() {
100+ _ = resp.Body.Close()
101+ }()
102
103- // body, err := httputil.DumpResponse(response, false)
104- // if err != nil {
105- // // copying the response body did not work
106- // return nil, err
107- // }
108- // t.Logger.Info("response", "dump", string(body))
109+ for k, vals := range resp.Header {
110+ for _, v := range vals {
111+ w.Header().Set(k, v)
112+ }
113+ }
114+ w.WriteHeader(resp.StatusCode)
115+ _, _ = io.Copy(w, resp.Body)
116+}
117
118- return response, err
119+type cachedHttp struct {
120+ Logger *slog.Logger
121+ Cache *httpcache.HttpCache
122 }
123
124 func (c *cachedHttp) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
125@@ -83,7 +103,7 @@ func (c *cachedHttp) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
126 }
127
128 if req.URL.Path == "/check" {
129- c.routes.Cfg.Logger.Info("proxying `/check` request to ash.pgs.sh", "query", req.URL.RawQuery)
130+ c.Logger.Info("proxying `/check` request to ash.pgs.sh", "query", req.URL.RawQuery)
131 req, _ := http.NewRequest("GET", "https://ash.pgs.sh/check?"+req.URL.RawQuery, nil)
132 req.Host = "pgs.sh"
133 // reqDump, _ := httputil.DumpRequestOut(req, true)
134@@ -91,27 +111,16 @@ func (c *cachedHttp) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
135
136 resp, err := http.DefaultClient.Do(req)
137 if err != nil {
138- c.routes.Cfg.Logger.Error("check request", "err", err)
139+ c.Logger.Error("check request", "err", err)
140 }
141 writer.WriteHeader(resp.StatusCode)
142- return
143+ defer func() {
144+ _ = resp.Body.Close()
145+ }()
146+ _, _ = io.Copy(writer, resp.Body)
147 }
148
149- _ = c.handler.ServeHTTP(writer, req, func(w http.ResponseWriter, r *http.Request) error {
150- url, _ := url.Parse(partialURL(r))
151-
152- c.routes.Cfg.Logger.Info("proxying request to ash.pgs.sh", "url", url.String())
153- defaultTransport := http.DefaultTransport.(*http.Transport)
154- oldDialContext := defaultTransport.DialContext
155- newTransport := CustomTransport{Transport: defaultTransport, Logger: c.routes.Cfg.Logger}
156- newTransport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
157- return oldDialContext(ctx, "tcp", "ash.pgs.sh:443")
158- }
159- proxy := httputil.NewSingleHostReverseProxy(url)
160- proxy.Transport = &newTransport
161- proxy.ServeHTTP(w, r)
162- return nil
163- })
164+ c.Cache.ServeHTTP(writer, req)
165 }
166
167 func partialURL(r *http.Request) string {
M
go.mod
+3,
-150
1@@ -26,9 +26,6 @@ require (
2 github.com/antoniomika/syncmap v1.0.0
3 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
4 github.com/containerd/console v1.0.5
5- github.com/darkweak/souin v1.7.8
6- github.com/darkweak/souin/plugins/souin/storages v1.7.8
7- github.com/darkweak/storages/core v0.0.16
8 github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
9 github.com/emersion/go-smtp v0.24.0
10 github.com/gkampitakis/go-snaps v0.5.15
11@@ -61,86 +58,37 @@ require (
12 go.abhg.dev/goldmark/hashtag v0.4.0
13 go.abhg.dev/goldmark/toc v0.12.0
14 golang.org/x/crypto v0.47.0
15- google.golang.org/protobuf v1.36.11
16 gopkg.in/yaml.v2 v2.4.0
17 modernc.org/sqlite v1.44.3
18 )
19
20 require (
21- cel.dev/expr v0.25.1 // indirect
22- cloud.google.com/go/auth v0.18.1 // indirect
23- cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
24- cloud.google.com/go/compute/metadata v0.9.0 // indirect
25 codeberg.org/emersion/go-scfg v0.1.0 // indirect
26 dario.cat/mergo v1.0.2 // indirect
27- filippo.io/edwards25519 v1.1.0 // indirect
28- github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
29 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
30- github.com/KimMachineGun/automemlimit v0.7.5 // indirect
31- github.com/Masterminds/goutils v1.1.1 // indirect
32- github.com/Masterminds/semver/v3 v3.4.0 // indirect
33- github.com/Masterminds/sprig/v3 v3.3.0 // indirect
34 github.com/Microsoft/go-winio v0.6.2 // indirect
35 github.com/PuerkitoBio/goquery v1.11.0 // indirect
36- github.com/RoaringBitmap/roaring v1.9.4 // indirect
37 github.com/andybalholm/cascadia v1.3.3 // indirect
38- github.com/antlabs/stl v0.0.2 // indirect
39- github.com/antlabs/timer v0.1.4 // indirect
40- github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
41- github.com/armon/go-metrics v0.4.1 // indirect
42- github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
43 github.com/aymerick/douceur v0.2.0 // indirect
44 github.com/beorn7/perks v1.0.1 // indirect
45- github.com/bits-and-blooms/bitset v1.24.4 // indirect
46- github.com/buraksezer/consistent v0.10.0 // indirect
47- github.com/buraksezer/olric v0.5.7 // indirect
48- github.com/bwmarrin/snowflake v0.3.0 // indirect
49- github.com/caddyserver/caddy/v2 v2.10.2 // indirect
50- github.com/caddyserver/certmagic v0.25.1 // indirect
51- github.com/caddyserver/zerossl v0.1.4 // indirect
52- github.com/ccoveille/go-safecast v1.6.1 // indirect
53 github.com/cenkalti/backoff/v4 v4.3.0 // indirect
54- github.com/cespare/xxhash v1.1.0 // indirect
55 github.com/cespare/xxhash/v2 v2.3.0 // indirect
56- github.com/chzyer/readline v1.5.1 // indirect
57 github.com/clipperhouse/stringish v0.1.1 // indirect
58 github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
59- github.com/cloudflare/circl v1.6.3 // indirect
60 github.com/containerd/errdefs v1.0.0 // indirect
61 github.com/containerd/errdefs/pkg v0.3.0 // indirect
62 github.com/containerd/log v0.1.0 // indirect
63 github.com/containerd/platforms v0.2.1 // indirect
64- github.com/coreos/go-oidc/v3 v3.17.0 // indirect
65- github.com/coreos/go-semver v0.3.1 // indirect
66- github.com/coreos/go-systemd/v22 v22.7.0 // indirect
67 github.com/cpuguy83/dockercfg v0.3.2 // indirect
68- github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
69- github.com/darkweak/go-esi v0.0.6 // indirect
70- github.com/darkweak/storages/badger v0.0.16 // indirect
71- github.com/darkweak/storages/etcd v0.0.16 // indirect
72- github.com/darkweak/storages/nats v0.0.16 // indirect
73- github.com/darkweak/storages/nuts v0.0.16 // indirect
74- github.com/darkweak/storages/olric v0.0.16 // indirect
75- github.com/darkweak/storages/otter v0.0.16 // indirect
76- github.com/darkweak/storages/redis v0.0.16 // indirect
77- github.com/darkweak/storages/simplefs v0.0.16 // indirect
78 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
79 github.com/delthas/go-libnp v0.2.0 // indirect
80 github.com/delthas/go-localeinfo v0.2.0 // indirect
81- github.com/dgraph-io/badger v1.6.2 // indirect
82- github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
83- github.com/dgraph-io/badger/v4 v4.9.1 // indirect
84- github.com/dgraph-io/ristretto v0.2.0 // indirect
85- github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
86- github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
87- github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
88 github.com/disintegration/imaging v1.6.2 // indirect
89 github.com/distribution/reference v0.6.0 // indirect
90 github.com/dlclark/regexp2 v1.11.5 // indirect
91 github.com/docker/docker v28.5.1+incompatible // indirect
92 github.com/docker/go-connections v0.6.0 // indirect
93 github.com/docker/go-units v0.5.0 // indirect
94- github.com/dolthub/maphash v0.1.0 // indirect
95 github.com/dsoprea/go-exif v0.0.0-20230826092837-6579e82b732d // indirect
96 github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d // indirect
97 github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 // indirect
98@@ -150,76 +98,34 @@ require (
99 github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 // indirect
100 github.com/dustin/go-humanize v1.0.1 // indirect
101 github.com/ebitengine/purego v0.8.4 // indirect
102- github.com/edsrzf/mmap-go v1.2.0 // indirect
103 github.com/felixge/httpsnoop v1.0.4 // indirect
104 github.com/forPelevin/gomoji v1.4.1 // indirect
105- github.com/francoispqt/gojay v1.2.13 // indirect
106- github.com/gammazero/deque v1.2.0 // indirect
107 github.com/gkampitakis/ciinfo v0.3.2 // indirect
108 github.com/gkampitakis/go-diff v1.3.2 // indirect
109 github.com/go-errors/errors v1.5.1 // indirect
110- github.com/go-jose/go-jose/v3 v3.0.4 // indirect
111- github.com/go-jose/go-jose/v4 v4.1.3 // indirect
112 github.com/go-logr/logr v1.4.3 // indirect
113 github.com/go-logr/stdr v1.2.2 // indirect
114 github.com/go-ole/go-ole v1.3.0 // indirect
115- github.com/go-redis/redis/v8 v8.11.5 // indirect
116 github.com/go-sql-driver/mysql v1.9.3 // indirect
117 github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
118 github.com/goccy/go-yaml v1.18.0 // indirect
119 github.com/godbus/dbus/v5 v5.2.2 // indirect
120- github.com/gofrs/flock v0.13.0 // indirect
121- github.com/gogo/protobuf v1.3.2 // indirect
122 github.com/golang/geo v0.0.0-20260129164528-943061e2742c // indirect
123- github.com/golang/protobuf v1.5.4 // indirect
124- github.com/golang/snappy v1.0.0 // indirect
125- github.com/google/btree v1.1.3 // indirect
126- github.com/google/cel-go v0.27.0 // indirect
127- github.com/google/flatbuffers v25.12.19+incompatible // indirect
128 github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef // indirect
129- github.com/google/s2a-go v0.1.9 // indirect
130- github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
131- github.com/googleapis/gax-go/v2 v2.17.0 // indirect
132 github.com/gorilla/css v1.0.1 // indirect
133 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
134- github.com/hashicorp/errwrap v1.1.0 // indirect
135- github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
136- github.com/hashicorp/go-metrics v0.5.4 // indirect
137- github.com/hashicorp/go-msgpack/v2 v2.1.5 // indirect
138- github.com/hashicorp/go-multierror v1.1.1 // indirect
139- github.com/hashicorp/go-sockaddr v1.0.7 // indirect
140- github.com/hashicorp/golang-lru v1.0.2 // indirect
141- github.com/hashicorp/logutils v1.0.0 // indirect
142- github.com/hashicorp/memberlist v0.5.4 // indirect
143- github.com/huandu/xstrings v1.5.0 // indirect
144- github.com/inconshreveable/mousetrap v1.1.0 // indirect
145- github.com/jackc/pgpassfile v1.0.0 // indirect
146- github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
147 github.com/jackc/pgx/v5 v5.8.0 // indirect
148- github.com/jackc/puddle/v2 v2.2.2 // indirect
149- github.com/jellydator/ttlcache/v3 v3.4.0 // indirect
150 github.com/json-iterator/go v1.1.12 // indirect
151 github.com/klauspost/compress v1.18.3 // indirect
152- github.com/klauspost/cpuid/v2 v2.3.0 // indirect
153 github.com/kr/fs v0.1.0 // indirect
154 github.com/kr/pretty v0.3.1 // indirect
155 github.com/kr/text v0.2.0 // indirect
156- github.com/libdns/libdns v1.1.1 // indirect
157 github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
158 github.com/magiconair/properties v1.8.10 // indirect
159- github.com/manifoldco/promptui v0.9.0 // indirect
160 github.com/maruel/natural v1.1.1 // indirect
161- github.com/mattn/go-colorable v0.1.14 // indirect
162 github.com/mattn/go-isatty v0.0.20 // indirect
163 github.com/mattn/go-runewidth v0.0.19 // indirect
164 github.com/mattn/go-sixel v0.0.8 // indirect
165- github.com/maypok86/otter v1.2.4 // indirect
166- github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
167- github.com/mholt/acmez/v3 v3.1.4 // indirect
168- github.com/miekg/dns v1.1.72 // indirect
169- github.com/mitchellh/copystructure v1.2.0 // indirect
170- github.com/mitchellh/go-ps v1.0.0 // indirect
171- github.com/mitchellh/reflectwalk v1.0.2 // indirect
172 github.com/mmcdole/goxpp v1.1.1 // indirect
173 github.com/mmcloughlin/md4 v0.1.2 // indirect
174 github.com/moby/docker-image-spec v1.3.1 // indirect
175@@ -232,105 +138,52 @@ require (
176 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
177 github.com/modern-go/reflect2 v1.0.2 // indirect
178 github.com/morikuni/aec v1.0.0 // indirect
179- github.com/mschoch/smat v0.2.0 // indirect
180 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
181- github.com/nats-io/nats.go v1.48.0 // indirect
182- github.com/nats-io/nkeys v0.4.15 // indirect
183- github.com/nats-io/nuid v1.0.1 // indirect
184 github.com/ncruces/go-strftime v1.0.0 // indirect
185 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e // indirect
186- github.com/nutsdb/nutsdb v1.1.0 // indirect
187- github.com/onsi/gomega v1.39.0 // indirect
188 github.com/opencontainers/go-digest v1.0.0 // indirect
189 github.com/opencontainers/image-spec v1.1.1 // indirect
190- github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
191- github.com/pierrec/lz4/v4 v4.1.25 // indirect
192 github.com/pkg/errors v0.9.1 // indirect
193 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
194 github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
195- github.com/pquerna/cachecontrol v0.2.0 // indirect
196 github.com/prometheus/client_model v0.6.2 // indirect
197 github.com/prometheus/common v0.67.5 // indirect
198 github.com/prometheus/procfs v0.19.2 // indirect
199- github.com/quic-go/qpack v0.5.1 // indirect
200- github.com/quic-go/quic-go v0.54.0 // indirect
201- github.com/redis/rueidis v1.0.71 // indirect
202 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
203 github.com/rivo/uniseg v0.4.7 // indirect
204 github.com/rogpeppe/go-internal v1.14.1 // indirect
205- github.com/rs/xid v1.6.0 // indirect
206- github.com/russross/blackfriday/v2 v2.1.0 // indirect
207- github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
208 github.com/shirou/gopsutil/v4 v4.25.6 // indirect
209- github.com/shopspring/decimal v1.4.0 // indirect
210- github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
211 github.com/sirupsen/logrus v1.9.4 // indirect
212- github.com/slackhq/nebula v1.9.5 // indirect
213- github.com/smallstep/certificates v0.28.4 // indirect
214- github.com/smallstep/cli-utils v0.12.1 // indirect
215- github.com/smallstep/linkedca v0.23.0 // indirect
216- github.com/smallstep/nosql v0.7.0 // indirect
217- github.com/smallstep/pkcs7 v0.2.1 // indirect
218- github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 // indirect
219- github.com/smallstep/truststore v0.13.0 // indirect
220 github.com/soniakeys/quant v1.0.0 // indirect
221- github.com/spf13/cast v1.10.0 // indirect
222- github.com/spf13/cobra v1.10.2 // indirect
223- github.com/spf13/pflag v1.0.10 // indirect
224 github.com/stretchr/testify v1.11.1 // indirect
225- github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
226- github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747 // indirect
227- github.com/tidwall/btree v1.8.1 // indirect
228 github.com/tidwall/gjson v1.18.0 // indirect
229 github.com/tidwall/match v1.2.0 // indirect
230 github.com/tidwall/pretty v1.2.1 // indirect
231- github.com/tidwall/redcon v1.6.2 // indirect
232 github.com/tidwall/sjson v1.2.5 // indirect
233 github.com/tklauser/go-sysconf v0.3.15 // indirect
234 github.com/tklauser/numcpus v0.10.0 // indirect
235- github.com/urfave/cli v1.22.17 // indirect
236- github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
237- github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
238- github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235 // indirect
239 github.com/yusufpapurcu/wmi v1.2.4 // indirect
240- github.com/zeebo/blake3 v0.2.4 // indirect
241- go.etcd.io/bbolt v1.4.3 // indirect
242- go.etcd.io/etcd/api/v3 v3.6.7 // indirect
243- go.etcd.io/etcd/client/pkg/v3 v3.6.7 // indirect
244- go.etcd.io/etcd/client/v3 v3.6.7 // indirect
245 go.opentelemetry.io/auto/sdk v1.2.1 // indirect
246 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
247 go.opentelemetry.io/otel v1.40.0 // indirect
248+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
249 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
250 go.opentelemetry.io/otel/metric v1.40.0 // indirect
251 go.opentelemetry.io/otel/trace v1.40.0 // indirect
252- go.step.sm/crypto v0.76.0 // indirect
253- go.uber.org/automaxprocs v1.6.0 // indirect
254- go.uber.org/mock v0.6.0 // indirect
255- go.uber.org/multierr v1.11.0 // indirect
256- go.uber.org/zap v1.27.1 // indirect
257- go.uber.org/zap/exp v0.3.0 // indirect
258+ go.opentelemetry.io/proto/otlp v1.7.0 // indirect
259 go.yaml.in/yaml/v2 v2.4.3 // indirect
260- go.yaml.in/yaml/v3 v3.0.4 // indirect
261- golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 // indirect
262 golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
263 golang.org/x/image v0.35.0 // indirect
264- golang.org/x/mod v0.32.0 // indirect
265 golang.org/x/net v0.49.0 // indirect
266- golang.org/x/oauth2 v0.34.0 // indirect
267 golang.org/x/sync v0.19.0 // indirect
268 golang.org/x/sys v0.40.0 // indirect
269- golang.org/x/term v0.39.0 // indirect
270 golang.org/x/text v0.33.0 // indirect
271 golang.org/x/time v0.14.0 // indirect
272- golang.org/x/tools v0.41.0 // indirect
273- google.golang.org/api v0.265.0 // indirect
274 google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
275 google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
276 google.golang.org/grpc v1.78.0 // indirect
277- google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 // indirect
278+ google.golang.org/protobuf v1.36.11 // indirect
279 gopkg.in/yaml.v3 v3.0.1 // indirect
280- howett.net/plist v1.0.1 // indirect
281 modernc.org/libc v1.67.7 // indirect
282 modernc.org/mathutil v1.7.1 // indirect
283 modernc.org/memory v1.11.0 // indirect
M
go.sum
+0,
-765
1@@ -1,64 +1,21 @@
2-cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
3-cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
4-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
5-cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
6-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
7-cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
8-cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
9-cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
10-cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
11-cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
12-cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
13-cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
14-cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
15-cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
16-cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
17-cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
18-cloud.google.com/go/kms v1.24.0 h1:SWltUuoPhTdv9q/P0YEAWQfoYT32O5HdfPgTiWMvrH8=
19-cloud.google.com/go/kms v1.24.0/go.mod h1:QDH3z2SJ50lfNOE8EokKC1G40i7I0f8xTMCoiptcb5g=
20-cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
21-cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
22 codeberg.org/emersion/go-scfg v0.1.0 h1:6dnGU0ZI4gX+O5rMjwhoaySItzHG710eXL5TIQKl+uM=
23 codeberg.org/emersion/go-scfg v0.1.0/go.mod h1:0nooW1ufBB4SlJEdTtiVN9Or+bnNM1icOkQ6Tbrq6O0=
24 dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
25 dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
26-dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
27-dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
28-dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
29-dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
30 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
31 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
32-git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
33 git.sr.ht/~delthas/senpai v0.4.1 h1:5CABpwJVzIBQaXtOngxG9TQDcxlaUqmGsXxlW+XcLuM=
34 git.sr.ht/~delthas/senpai v0.4.1/go.mod h1:mzdu4o3wANA6cYzRnrz3w+uGPHA2z3j02JDrr/M3Myc=
35 git.sr.ht/~rockorager/vaxis v0.15.1-0.20251218121515-cdf898cf10c7 h1:Hik9uYRclWV+mt5PnGc41aeGX6sh8MGzWcBnJt9EzpY=
36 git.sr.ht/~rockorager/vaxis v0.15.1-0.20251218121515-cdf898cf10c7/go.mod h1:ptCAzb19xAhqhfAmCsN9Xnxdx01vlXkwZcuMYPnyqzE=
37 github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
38 github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
39-github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
40-github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
41 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
42 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
43-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
44-github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
45-github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
46-github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43DwZrJqVwyveTk=
47-github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
48-github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
49-github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
50-github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
51-github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
52-github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
53-github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
54 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
55 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
56-github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
57-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
58 github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
59 github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
60-github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
61-github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ=
62-github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
63 github.com/adhocore/gronx v1.19.6 h1:5KNVcoR9ACgL9HhEqCm5QXsab/gI4QDIybTAWcXDKDc=
64 github.com/adhocore/gronx v1.19.6/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg=
65 github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
66@@ -69,114 +26,24 @@ github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9c
67 github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
68 github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
69 github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
70-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
71-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
72-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
73-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
74-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
75 github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
76 github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
77-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
78-github.com/antlabs/stl v0.0.2 h1:sna1AXR5yIkNE9lWhCcKbheFJSVfCa3vugnGyakI79s=
79-github.com/antlabs/stl v0.0.2/go.mod h1:kKrO4xrn9cfS1mJVo+/BqePZjAYMXqD0amGF2Ouq7ac=
80-github.com/antlabs/timer v0.1.4 h1:MHdE00MDnNfhJCmqSOdLXs35uGNwfkMwfbynxrGmQ1c=
81-github.com/antlabs/timer v0.1.4/go.mod h1:mpw4zlD5KVjstEyUDp43DGLWsY076Mdo4bS78NTseRE=
82-github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
83-github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
84 github.com/antoniomika/syncmap v1.0.0 h1:iFSfbQFQOvHZILFZF+hqWosO0no+W9+uF4y2VEyMKWU=
85 github.com/antoniomika/syncmap v1.0.0/go.mod h1:fK2829foEYnO4riNfyUn0SHQZt4ue3DStYjGU+sJj38=
86 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
87 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
88-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
89-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
90-github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
91-github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
92-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
93-github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
94-github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
95-github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
96-github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
97-github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY=
98-github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY=
99-github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8=
100-github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw=
101-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=
102-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=
103-github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=
104-github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=
105-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik=
106-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM=
107-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
108-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
109-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
110-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
111-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY=
112-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU=
113-github.com/aws/aws-sdk-go-v2/service/kms v1.49.5 h1:DKibav4XF66XSeaXcrn9GlWGHos6D/vJ4r7jsK7z5CE=
114-github.com/aws/aws-sdk-go-v2/service/kms v1.49.5/go.mod h1:1SdcmEGUEQE1mrU2sIgeHtcMSxHuybhPvuEPANzIDfI=
115-github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y=
116-github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M=
117-github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk=
118-github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=
119-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds=
120-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=
121-github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ=
122-github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ=
123-github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
124-github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
125 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
126 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
127-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
128-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
129 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
130 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
131-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
132-github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
133-github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
134-github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
135-github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
136-github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
137-github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
138-github.com/buraksezer/consistent v0.10.0 h1:hqBgz1PvNLC5rkWcEBVAL9dFMBWz6I0VgUCW25rrZlU=
139-github.com/buraksezer/consistent v0.10.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw=
140-github.com/buraksezer/olric v0.5.7 h1:K8ypVViiPkXiqBz3UyDAY99cHvvofAR65fmH7ElPEWE=
141-github.com/buraksezer/olric v0.5.7/go.mod h1:S1R+9Zt7P9TCbvQZvY/RYuRehLLRPDfbJNkukQsLJ4k=
142-github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
143-github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
144-github.com/caddyserver/caddy/v2 v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8=
145-github.com/caddyserver/caddy/v2 v2.10.2/go.mod h1:TXLQHx+ev4HDpkO6PnVVHUbL6OXt6Dfe7VcIBdQnPL0=
146-github.com/caddyserver/certmagic v0.25.1 h1:4sIKKbOt5pg6+sL7tEwymE1x2bj6CHr80da1CRRIPbY=
147-github.com/caddyserver/certmagic v0.25.1/go.mod h1:VhyvndxtVton/Fo/wKhRoC46Rbw1fmjvQ3GjHYSQTEY=
148-github.com/caddyserver/zerossl v0.1.4 h1:CVJOE3MZeFisCERZjkxIcsqIH4fnFdlYWnPYeFtBHRw=
149-github.com/caddyserver/zerossl v0.1.4/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
150-github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q=
151-github.com/ccoveille/go-safecast v1.6.1/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8=
152 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
153 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
154-github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
155-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
156-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
157-github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
158 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
159 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
160-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
161-github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
162-github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
163-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
164-github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
165-github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
166-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
167-github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
168-github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
169-github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
170-github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
171-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
172 github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
173 github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
174 github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
175 github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
176-github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
177-github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
178 github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
179 github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
180 github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
181@@ -187,49 +54,11 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
182 github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
183 github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
184 github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
185-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
186-github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
187-github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
188-github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
189-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
190-github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
191-github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
192-github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
193-github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
194-github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
195 github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
196 github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
197-github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
198-github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
199-github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
200-github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
201 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
202 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
203 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
204-github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8=
205-github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk=
206-github.com/darkweak/souin v1.7.8 h1:yENXy0oSzknu0Uzz/bO01rYLCcmAITlDgvEEtbT10PE=
207-github.com/darkweak/souin v1.7.8/go.mod h1:kR97vndGl0jEWmHazJXNTdVkOzQis05kUdK9EGKUf+k=
208-github.com/darkweak/souin/plugins/souin/storages v1.7.8 h1:7x+FU8CU7EKr96hnMTh6wuX0OHfyBBkSr03X2WY7MRs=
209-github.com/darkweak/souin/plugins/souin/storages v1.7.8/go.mod h1:3OmumRhtRKzIalqlqIHDuvaaZMSJDoO5q1b1RRnCZRw=
210-github.com/darkweak/storages/badger v0.0.16 h1:5NNHescfvT0YqqgeCvRB9cFtCeCvEetPliCYOI+aGqo=
211-github.com/darkweak/storages/badger v0.0.16/go.mod h1:hpKJ7pGI27elfu1IJ1OnsBpsyYx627oeebtL+DbN0mI=
212-github.com/darkweak/storages/core v0.0.16 h1:f7+XY8MJaKiukcOC5v48pTSpD0zpzEu4FnfgOAPbyF0=
213-github.com/darkweak/storages/core v0.0.16/go.mod h1:3qJqrenCLpu+0bWPOAq36CmGpzL3SGWAz6KGZGnur1U=
214-github.com/darkweak/storages/etcd v0.0.16 h1:l9p4+tinfc6AUITHld3g5lzBbLokfIudowEIPK2E1+U=
215-github.com/darkweak/storages/etcd v0.0.16/go.mod h1:UnkdHq06kZS9QeQ9ItNIN9Bl7YZZVdVclX+kcvAd+k8=
216-github.com/darkweak/storages/nats v0.0.16 h1:9KaHq9q+tK4Tm1JjAaj6YFGy8/9u5AJK9O9tFx+NvYI=
217-github.com/darkweak/storages/nats v0.0.16/go.mod h1:yWkupn1y7R4wPzLgqgXc65c50X+7q94QQ9PCxwLqIWA=
218-github.com/darkweak/storages/nuts v0.0.16 h1:EJ6b8YbXjK7PsdE6mMRKKs6qj3G+5dW4PfmzCNXrdsA=
219-github.com/darkweak/storages/nuts v0.0.16/go.mod h1:MPNNFqFAuiFLDsPcsxXSG5h5dRea1Wd6cXAzHXnFD1Q=
220-github.com/darkweak/storages/olric v0.0.16 h1:gv7S+eWAmjnYbhMVEmltEHG6Kzacil7nTXpKfXT47F4=
221-github.com/darkweak/storages/olric v0.0.16/go.mod h1:PyZuWYNVN0y4lFNN1ZqEWxOW59nzQfXU6aiJnQegxcM=
222-github.com/darkweak/storages/otter v0.0.16 h1:SFPlKIapzcrfSp16C3/K6fzIqRblqDWE/xyse9VhJFE=
223-github.com/darkweak/storages/otter v0.0.16/go.mod h1:FSqYWBBSoojOZEH4hr19TmBnBxyH+ZQ4V8tGp4xVBGw=
224-github.com/darkweak/storages/redis v0.0.16 h1:dlDUpco8V6DnOwqS5oYFIi4aLBtto9u0ZyJqbpyy22U=
225-github.com/darkweak/storages/redis v0.0.16/go.mod h1:kBchmscY7C7URnruedPgb3jcijV1COzszfOe/KVTk5g=
226-github.com/darkweak/storages/simplefs v0.0.16 h1:9duSjUTKjZWUv2Spk5+3MueENwxElEWlp0Tij2CihEY=
227-github.com/darkweak/storages/simplefs v0.0.16/go.mod h1:TMjB3kGbYHZPZoRGTCW+Wph9taF2/ym0cUfP0jYU47s=
228 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
229 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
230 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
231@@ -238,23 +67,6 @@ github.com/delthas/go-libnp v0.2.0 h1:OVll7Z9CER0rYpgiglXN1QvuNtkyY5X9uzLqm+xDst
232 github.com/delthas/go-libnp v0.2.0/go.mod h1:LHUapIgbv1vBcZJU4cJRPkyslKhKe7GNRF38yUbl9QA=
233 github.com/delthas/go-localeinfo v0.2.0 h1:0K5F5GQ5p4ealZjuBs4eubHLOIL21/YWSxs557hHZ+M=
234 github.com/delthas/go-localeinfo v0.2.0/go.mod h1:sG54BxlyQgIskYURLrg7mvhoGBe0Qq12DNtYRALwNa4=
235-github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
236-github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
237-github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
238-github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
239-github.com/dgraph-io/badger/v4 v4.9.1 h1:DocZXZkg5JJHJPtUErA0ibyHxOVUDVoXLSCV6t8NC8w=
240-github.com/dgraph-io/badger/v4 v4.9.1/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0=
241-github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
242-github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
243-github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
244-github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
245-github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
246-github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
247-github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
248-github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
249-github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
250-github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
251-github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
252 github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
253 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
254 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
255@@ -269,8 +81,6 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM
256 github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
257 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
258 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
259-github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
260-github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
261 github.com/dsoprea/go-exif v0.0.0-20230826092837-6579e82b732d h1:ygcRCGNKuEiA98k7X35hknEN8RIRUF1jrz7k1rZCvsk=
262 github.com/dsoprea/go-exif v0.0.0-20230826092837-6579e82b732d/go.mod h1:lOaOt7+UEppOgyvRy749v3do836U/hw0YVJNjoyPaEs=
263 github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
264@@ -295,40 +105,24 @@ github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3
265 github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 h1:/py11NlxDaOxkT9OKN+gXgT+QOH5xj1ZRoyusfRIlo4=
266 github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349/go.mod h1:KVK+/Hul09ujXAGq+42UBgCTnXkiJZRnLYdURGjQUwo=
267 github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
268-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
269 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
270 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
271 github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
272 github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
273-github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
274-github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
275 github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
276 github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
277 github.com/emersion/go-smtp v0.24.0 h1:g6AfoF140mvW0vLNPD/LuCBLEAdlxOjIXqbIkJIS6Wk=
278 github.com/emersion/go-smtp v0.24.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
279-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
280 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
281 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
282-github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
283 github.com/forPelevin/gomoji v1.4.1 h1:7U+Bl8o6RV/dOQz7coQFWj/jX6Ram6/cWFOuFDEPEUo=
284 github.com/forPelevin/gomoji v1.4.1/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ=
285-github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
286-github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
287-github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
288-github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
289-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
290-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
291-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
292-github.com/gammazero/deque v1.2.0 h1:scEFO8Uidhw6KDU5qg1HA5fYwM0+us2qdeJqm43bitU=
293-github.com/gammazero/deque v1.2.0/go.mod h1:JVrR+Bj1NMQbPnYclvDlvSX0nVGReLrQZ0aUMuWLctg=
294-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
295 github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
296 github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
297 github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
298 github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
299 github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
300 github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
301-github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
302 github.com/go-andiamo/splitter v1.2.5 h1:P3NovWMY2V14TJJSolXBvlOmGSZo3Uz+LtTl2bsV/eY=
303 github.com/go-andiamo/splitter v1.2.5/go.mod h1:8WHU24t9hcMKU5FXDQb1hysSEC/GPuivIp0uKY1J8gw=
304 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
305@@ -336,16 +130,6 @@ github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE
306 github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
307 github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
308 github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
309-github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
310-github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
311-github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
312-github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
313-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
314-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
315-github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
316-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
317-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
318-github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
319 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
320 github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
321 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
322@@ -354,13 +138,9 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
323 github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
324 github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
325 github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
326-github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
327-github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
328 github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
329 github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
330 github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
331-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
332-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
333 github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
334 github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
335 github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
336@@ -368,135 +148,32 @@ github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
337 github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
338 github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
339 github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
340-github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
341-github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
342-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
343-github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
344-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
345 github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
346 github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
347 github.com/golang/geo v0.0.0-20260129164528-943061e2742c h1:ysO2h2Odnl1AJM1I2Lm/fa6JvO0pECMSt2CwBaa+ITo=
348 github.com/golang/geo v0.0.0-20260129164528-943061e2742c/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
349-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
350-github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
351-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
352-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
353-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
354-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
355-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
356-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
357-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
358-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
359-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
360-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
361-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
362-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
363-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
364-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
365-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
366-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
367-github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
368-github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
369-github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
370-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
371-github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
372-github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
373-github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=
374-github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw=
375-github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
376-github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
377-github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
378-github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
379-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
380-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
381-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
382-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
383-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
384-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
385-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
386 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
387 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
388 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
389-github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
390-github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
391-github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
392-github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
393-github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
394-github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
395-github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
396-github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
397 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
398-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
399-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
400-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
401 github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef h1:xpF9fUHpoIrrjX24DURVKiwHcFpw19ndIs+FwTSMbno=
402 github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
403 github.com/google/renameio/v2 v2.0.2 h1:qKZs+tfn+arruZZhQ7TKC/ergJunuJicWS6gLDt/dGw=
404 github.com/google/renameio/v2 v2.0.2/go.mod h1:OX+G6WHHpHq3NVj7cAOleLOwJfcQ1s3uUJQCrr78SWo=
405-github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
406-github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
407 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
408 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
409-github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao=
410-github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=
411-github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
412-github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
413-github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
414-github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
415-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
416 github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
417 github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
418 github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
419 github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
420 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
421 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
422-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
423-github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
424 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
425 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
426-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
427-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
428-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
429-github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
430-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
431-github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
432-github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
433-github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY=
434-github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI=
435-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
436-github.com/hashicorp/go-msgpack/v2 v2.1.5 h1:Ue879bPnutj/hXfmUk6s/jtIK90XxgiUIcXRl656T44=
437-github.com/hashicorp/go-msgpack/v2 v2.1.5/go.mod h1:bjCsRXpZ7NsJdk45PoCQnzRGDaK8TKm5ZnDI/9y3J4M=
438-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
439-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
440-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
441-github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
442-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
443-github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
444-github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
445-github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
446-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
447-github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
448-github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
449-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
450-github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
451-github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
452 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
453 github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
454-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
455-github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
456-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
457-github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
458-github.com/hashicorp/memberlist v0.5.4 h1:40YY+3qq2tAUhZIMEK8kqusKZBBjdwJ3NUjvYkcxh74=
459-github.com/hashicorp/memberlist v0.5.4/go.mod h1:OgN6xiIo6RlHUWk+ALjP9e32xWCoQrsOCmHrWCm2MWA=
460 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
461 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
462-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
463-github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
464-github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
465-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
466-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
467-github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
468-github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
469 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
470 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
471 github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
472@@ -505,41 +182,17 @@ github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
473 github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
474 github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
475 github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
476-github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
477-github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY=
478-github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4=
479 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
480 github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
481 github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
482-github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
483-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
484-github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
485-github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
486-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
487 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
488 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
489-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
490-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
491-github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
492-github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
493-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
494-github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
495 github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
496 github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
497-github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
498-github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
499-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
500-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
501 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
502 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
503-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
504-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
505-github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
506 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
507 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
508-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
509-github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
510-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
511 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
512 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
513 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
514@@ -547,25 +200,14 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
515 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
516 github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI=
517 github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
518-github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
519-github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
520 github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
521 github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
522-github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
523-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
524 github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
525 github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
526-github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
527-github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
528-github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
529 github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
530 github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
531 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
532 github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
533-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
534-github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
535-github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
536-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
537 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
538 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
539 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
540@@ -575,32 +217,10 @@ github.com/mattn/go-sixel v0.0.8 h1:H0bBGQVOJoSvzvtTgCInxvg1IZiNlTcIIIx8A6uvjpQ=
541 github.com/mattn/go-sixel v0.0.8/go.mod h1:wbDSbrwpykVI1qEHyjZYsDgaJTwpVg9wSwmmh2slnBw=
542 github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
543 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
544-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
545-github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc=
546-github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
547 github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
548 github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
549-github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
550-github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
551-github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ=
552-github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
553-github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
554 github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
555 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
556-github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
557-github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
558-github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
559-github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
560-github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
561-github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
562-github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
563-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
564-github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
565-github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
566-github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
567-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
568-github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
569-github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
570 github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
571 github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE=
572 github.com/mmcdole/goxpp v1.1.1 h1:RGIX+D6iQRIunGHrKqnA2+700XMCnNv0bAOOv5MUhx8=
573@@ -626,71 +246,27 @@ github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3
574 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
575 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
576 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
577-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
578-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
579 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
580 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
581 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
582 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
583-github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
584-github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
585 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
586 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
587-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
588-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
589-github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
590-github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
591-github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
592-github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=
593-github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
594-github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
595 github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
596 github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
597-github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
598-github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
599 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577 h1:hVmVNttSLNloGsbFKVXAUHonXTd8KKrv30U/8UkloKI=
600 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577/go.mod h1:G3Cu1AW+dmRLDFpOi8eUAfc3cGoRHUjTkGjeRcndgl4=
601 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e h1:76Dng5ms0fR+26doKZAvNqhi2UPfnLxGfPIDEr+BBlM=
602 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e/go.mod h1:nZBDA7+RD63GDJwjZmxhxac65MJqiCIHUUUvdYOsFkk=
603-github.com/nutsdb/nutsdb v1.1.0 h1:fNGFzBHGqF2mB5BF8Qk8W94c3/ZzwdCdKAH7azwx70Y=
604-github.com/nutsdb/nutsdb v1.1.0/go.mod h1:aKCtgSprZf2Mp1dIQD00Iya3DttoTErSSOnRx5ZtpAs=
605-github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
606-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
607-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
608-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
609-github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
610-github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
611-github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
612-github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
613-github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
614-github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
615-github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
616-github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
617-github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
618-github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
619-github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
620 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
621 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
622 github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
623 github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
624-github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
625-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
626-github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
627-github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
628-github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
629-github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
630-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
631-github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
632-github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
633 github.com/picosh/go-rsync-receiver v0.0.0-20250304201040-fcc11dd22d79 h1:MyB9P43hlQ6A2FoP9LGeiTBL3WKToW4gcWd6lQPg/Zg=
634 github.com/picosh/go-rsync-receiver v0.0.0-20250304201040-fcc11dd22d79/go.mod h1:4ZICsr6bESoHP8He9DqROlZiMw4hHHjcbDzhtTTDQzA=
635 github.com/picosh/utils v0.0.0-20260125160622-5c3a9e231ec6 h1:9KfCtfcx7vrSyGU1K9whdE1crll9Aq+nAZ6c0FzuzvE=
636 github.com/picosh/utils v0.0.0-20260125160622-5c3a9e231ec6/go.mod h1:HogYEyJ43IGXrOa3D/kjM1pkzNAyh+pejRyv8Eo//pk=
637-github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
638-github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
639 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
640-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
641-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
642 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
643 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
644 github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
645@@ -698,47 +274,16 @@ github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1Hbe
646 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
647 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
648 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
649-github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
650 github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
651 github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
652-github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
653-github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
654-github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
655-github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
656-github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
657-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
658-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
659-github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
660-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
661-github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
662 github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
663 github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
664-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
665-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
666-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
667 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
668 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
669-github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
670-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
671-github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
672-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
673-github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
674 github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
675 github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
676-github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
677-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
678-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
679-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
680-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
681-github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
682 github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
683 github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
684-github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
685-github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
686-github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
687-github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
688-github.com/redis/rueidis v1.0.71 h1:pODtnAR5GAB7j4ekhldZ29HKOxe4Hph0GTDGk1ayEQY=
689-github.com/redis/rueidis v1.0.71/go.mod h1:lfdcZzJ1oKGKL37vh9fO3ymwt+0TdjkkUCJxbgpmcgQ=
690 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
691 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
692 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
693@@ -747,125 +292,29 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
694 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
695 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
696 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
697-github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
698-github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
699-github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
700-github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
701-github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
702-github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
703 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
704 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
705-github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
706-github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
707 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
708-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
709-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
710-github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
711 github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
712 github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
713-github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
714-github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
715-github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
716-github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
717-github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
718-github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
719-github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
720-github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
721-github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
722-github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
723-github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
724-github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
725-github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
726-github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
727-github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
728-github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
729-github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
730-github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
731-github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
732-github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
733-github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
734-github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
735-github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
736-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
737-github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
738-github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
739 github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d h1:4FkGkGts6gLznca6fgclIvbupwbq543mb/fFkog4VIg=
740 github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d/go.mod h1:fTTj1EOmRdtuwYw3jF/1X2dTa0N1BdbZhrpA21N/S4I=
741-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
742-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
743-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
744 github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
745 github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
746-github.com/slackhq/nebula v1.9.5 h1:ZrxcvP/lxwFglaijmiwXLuCSkybZMJnqSYI1S8DtGnY=
747-github.com/slackhq/nebula v1.9.5/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
748-github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
749-github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
750-github.com/smallstep/certificates v0.28.4 h1:JTU6/A5Xes6m+OsR6fw1RACSA362vJc9SOFVG7poBEw=
751-github.com/smallstep/certificates v0.28.4/go.mod h1:LUqo+7mKZE7FZldlTb0zhU4A0bq4G4+akieFMcTaWvA=
752-github.com/smallstep/cli-utils v0.12.1 h1:D9QvfbFqiKq3snGZ2xDcXEFrdFJ1mQfPHZMq/leerpE=
753-github.com/smallstep/cli-utils v0.12.1/go.mod h1:skV2Neg8qjiKPu2fphM89H9bIxNpKiiRTnX9Q6Lc+20=
754-github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4=
755-github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
756-github.com/smallstep/linkedca v0.23.0 h1:5W/7EudlK1HcCIdZM68dJlZ7orqCCCyv6bm2l/0JmLU=
757-github.com/smallstep/linkedca v0.23.0/go.mod h1:7cyRM9soAYySg9ag65QwytcgGOM+4gOlkJ/YA58A9E8=
758-github.com/smallstep/nosql v0.7.0 h1:YiWC9ZAHcrLCrayfaF+QJUv16I2bZ7KdLC3RpJcnAnE=
759-github.com/smallstep/nosql v0.7.0/go.mod h1:H5VnKMCbeq9QA6SRY5iqPylfxLfYcLwvUff3onQ8+HU=
760-github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA=
761-github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0=
762-github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 h1:k23+s51sgYix4Zgbvpmy+1ZgXLjr4ZTkBTqXmpnImwA=
763-github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492/go.mod h1:QQhwLqCS13nhv8L5ov7NgusowENUtXdEzdytjmJHdZQ=
764-github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
765-github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
766 github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
767 github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
768-github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
769-github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
770-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
771-github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
772-github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
773-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
774-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
775-github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
776-github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
777-github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
778-github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
779-github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
780-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
781-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
782-github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
783-github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
784-github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
785-github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
786 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
787-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
788-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
789-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
790 github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
791 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
792-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
793 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
794-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
795-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
796 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
797 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
798-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
799-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
800-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
801-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
802 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
803 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
804-github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
805-github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
806-github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747 h1:RnBbFMmodYzhC6adOjTbtUQXyzV8dcvKYbolzs6Qch0=
807-github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747/go.mod h1:ejPAJui3kVK4u5TgMtqtXlWf5HnKh9fLy5kvpaeuas0=
808-github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
809 github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
810 github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
811 github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 h1:s2bIayFXlbDFexo96y+htn7FzuhpXLYJNnIuglNKqOk=
812 github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0/go.mod h1:h+u/2KoREGTnTl9UwrQ/g+XhasAT8E6dClclAADeXoQ=
813-github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
814-github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
815-github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
816 github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
817 github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
818 github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
819@@ -875,33 +324,14 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
820 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
821 github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
822 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
823-github.com/tidwall/redcon v1.6.2 h1:5qfvrrybgtO85jnhSravmkZyC0D+7WstbfCs3MmPhow=
824-github.com/tidwall/redcon v1.6.2/go.mod h1:p5Wbsgeyi2VSTBWOcA5vRXrOb9arFTcU2+ZzFjqV75Y=
825 github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
826 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
827 github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
828 github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
829 github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
830 github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
831-github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
832-github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
833-github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
834-github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
835-github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
836-github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
837-github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
838-github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
839-github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
840-github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
841-github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
842 github.com/x-way/crawlerdetect v0.2.30 h1:U43R8+TZ7AZwBZehWRPdRdW53NmPoVZSOptevJKo1mE=
843 github.com/x-way/crawlerdetect v0.2.30/go.mod h1:BPHLsB3FOuiwoWyhAvnqeiUSAEKd34O7BcsTCcxHRj4=
844-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
845-github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235 h1:w0si+uee0iAaCJO9q86T6yrhdadgcsoNuh47LrUykzg=
846-github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235/go.mod h1:MR4+0R6A9NS5IABnIM3384FfOq8QFVnm7WDrBOhIaMU=
847-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
848-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
849-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
850 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
851 github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
852 github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
853@@ -912,31 +342,14 @@ github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUei
854 github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
855 github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
856 github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
857-github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
858-github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
859-github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
860-github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
861-github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
862-github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
863 go.abhg.dev/goldmark/anchor v0.2.0 h1:RQZTodRc6VHSUoQYKFlyH0pokbhk1klwUuGgDmjGp2E=
864 go.abhg.dev/goldmark/anchor v0.2.0/go.mod h1:Ym74zBV+QBKxK9ITOty680N9FT8otgGYvtYXroJUWms=
865 go.abhg.dev/goldmark/hashtag v0.4.0 h1:jkYcsIRUpog/rhWMnIOjj14qj8p5p4um+IzPbPUxFww=
866 go.abhg.dev/goldmark/hashtag v0.4.0/go.mod h1:5DQwSo2dEHKtsfXOC1+Po7WgVwR5IoGjgeVIerddz9g=
867 go.abhg.dev/goldmark/toc v0.12.0 h1:kiEBBIOB7jEzNpXmGdiL2L/zGSELKw/p3mosm2+RSuo=
868 go.abhg.dev/goldmark/toc v0.12.0/go.mod h1:kskbM5l9y8wOFEFfyEe9wnwhWeykvmHB6xEPCVrZIvg=
869-go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
870-go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
871-go.etcd.io/etcd/api/v3 v3.6.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0=
872-go.etcd.io/etcd/api/v3 v3.6.7/go.mod h1:xJ81TLj9hxrYYEDmXTeKURMeY3qEDN24hqe+q7KhbnI=
873-go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/AhBVs=
874-go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q=
875-go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U=
876-go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE=
877-go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
878 go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
879 go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
880-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
881-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
882 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
883 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
884 go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
885@@ -955,57 +368,23 @@ go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZY
886 go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
887 go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
888 go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
889-go.step.sm/crypto v0.76.0 h1:K23BSaeoiY7Y5dvvijTeYC9EduDBetNwQYMBwMhi1aA=
890-go.step.sm/crypto v0.76.0/go.mod h1:PXYJdKkK8s+GHLwLguFaLxHNAFsFL3tL1vSBrYfey5k=
891-go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
892-go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
893 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
894 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
895-go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
896-go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
897-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
898-go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
899-go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
900-go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
901-go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
902-go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
903 go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
904 go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
905-go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
906-go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
907-go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
908-golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
909-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
910-golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
911-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
912 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
913-golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
914-golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
915-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
916-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
917 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
918 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
919 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
920 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
921-golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
922 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
923-golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
924 golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
925 golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
926-golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 h1:EBHQuS9qI8xJ96+YRgVV2ahFLUYbWpt1rf3wPfXN2wQ=
927-golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
928-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
929 golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
930 golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
931 golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
932 golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
933 golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
934-golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
935-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
936-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
937-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
938-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
939-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
940 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
941 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
942 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
943@@ -1013,110 +392,40 @@ golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
944 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
945 golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
946 golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
947-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
948-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
949-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
950-golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
951-golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
952-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
953-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
954-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
955-golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
956-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
957-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
958 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
959-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
960 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
961-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
962 golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
963 golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
964 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
965-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
966 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
967-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
968-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
969 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
970-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
971-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
972-golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
973 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
974 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
975 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
976 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
977 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
978 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
979-golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
980 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
981 golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
982 golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
983-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
984-golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
985-golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
986-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
987-golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
988-golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
989-golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
990-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
991-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
992-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
993-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
994 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
995-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
996-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
997-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
998-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
999 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1000 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1001 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
1002 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1003 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1004-golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1005 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1006-golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1007 golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
1008 golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
1009-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1010-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1011-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1012-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1013-golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1014-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1015-golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1016-golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1017 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1018-golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1019-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1020-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1021-golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1022-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1023 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1024-golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1025-golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1026-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1027-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1028-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1029-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1030-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1031 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1032-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1033-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1034-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1035 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1036 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1037-golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1038-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1039-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1040-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1041-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1042-golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1043 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1044 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1045-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1046-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1047-golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1048 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1049 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1050-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1051 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1052 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1053 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1054@@ -1124,9 +433,7 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1055 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1056 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1057 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1058-golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1059 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1060-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1061 golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
1062 golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
1063 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
1064@@ -1137,42 +444,24 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
1065 golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
1066 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
1067 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
1068-golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
1069 golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
1070-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
1071 golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
1072 golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
1073 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1074-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1075-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
1076 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
1077-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
1078 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
1079 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
1080 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
1081 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
1082 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
1083 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
1084-golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
1085 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
1086-golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
1087 golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
1088 golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
1089-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1090-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1091 golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
1092 golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
1093-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1094 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1095-golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1096-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1097-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
1098-golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1099 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1100-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
1101-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
1102-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
1103-golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
1104 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
1105 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
1106 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
1107@@ -1180,63 +469,17 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
1108 golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
1109 golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
1110 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1111-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1112-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1113-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1114-gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
1115-gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
1116-google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
1117-google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
1118-google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
1119-google.golang.org/api v0.265.0 h1:FZvfUdI8nfmuNrE34aOWFPmLC+qRBEiNm3JdivTvAAU=
1120-google.golang.org/api v0.265.0/go.mod h1:uAvfEl3SLUj/7n6k+lJutcswVojHPp2Sp08jWCu8hLY=
1121-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
1122-google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1123-google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1124-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1125-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
1126-google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
1127-google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
1128-google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
1129-google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
1130-google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
1131-google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
1132 google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0=
1133 google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE=
1134 google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
1135 google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
1136-google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
1137-google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
1138-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
1139-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
1140 google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
1141 google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
1142-google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110=
1143-google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc=
1144-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
1145-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
1146-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
1147-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
1148-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
1149-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
1150-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
1151-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
1152 google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
1153 google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
1154-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
1155 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1156-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1157 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
1158 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
1159-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
1160-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
1161-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
1162-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
1163-gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
1164-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1165-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1166-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1167-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1168 gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1169 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1170 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
1171@@ -1246,12 +489,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1172 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1173 gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
1174 gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
1175-grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
1176-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1177-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1178-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1179-howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
1180-howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
1181 modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
1182 modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
1183 modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
1184@@ -1284,5 +521,3 @@ mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
1185 mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
1186 pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
1187 pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
1188-sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
1189-sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
+0,
-5
1@@ -12,7 +12,6 @@ import (
2 )
3
4 type PgsConfig struct {
5- CacheControl string
6 CacheTTL time.Duration
7 Domain string
8 MaxAssetSize int64
9@@ -75,15 +74,11 @@ func NewPgsConfig(logger *slog.Logger, dbpool pgsdb.PgsDB, st storage.StorageSer
10 if err != nil {
11 cacheTTL = 600 * time.Second
12 }
13- cacheControl := shared.GetEnv(
14- "PGS_CACHE_CONTROL",
15- fmt.Sprintf("max-age=%d", int(cacheTTL.Seconds())))
16
17 sshHost := shared.GetEnv("PGS_SSH_HOST", "0.0.0.0")
18 sshPort := shared.GetEnv("PGS_SSH_PORT", "2222")
19
20 cfg := PgsConfig{
21- CacheControl: cacheControl,
22 CacheTTL: cacheTTL,
23 Domain: domain,
24 MaxAssetSize: maxAssetSize,
+0,
-183
1@@ -1,183 +0,0 @@
2-package pgs
3-
4-import (
5- "bytes"
6- "log/slog"
7- "net/http"
8- "net/http/httptest"
9- "strings"
10- "testing"
11-
12- "github.com/picosh/pico/pkg/shared"
13- "github.com/picosh/pico/pkg/storage"
14-)
15-
16-// TestLargeFileNotTruncatedOnCacheHit reproduces the Souin truncation bug.
17-// Large files (3MB) are cached as truncated (~4KB) on the second request.
18-//
19-// Bug behavior:
20-// - First request (cache miss): Returns full 3MB file ✓
21-// - Second request (cache hit): Returns only ~4KB (truncated!) ✗
22-//
23-// Root cause: Souin's Store() snapshots the buffer mid-stream while
24-// io.Copy() is still writing data, resulting in partial cache entries.
25-func TestLargeFileNotTruncatedOnCacheHit(t *testing.T) {
26- logger := slog.Default()
27- dbpool := NewPgsDb(logger)
28- bucketName := shared.GetAssetBucketName(dbpool.Users[0].ID)
29-
30- // 3MB payload - reproduces the exact bug scenario
31- largePayload := bytes.Repeat([]byte("x"), 3*1024*1024)
32- expectedSize := len(largePayload)
33-
34- st, err := storage.NewStorageMemory(map[string]map[string]string{
35- bucketName: {
36- "/test/large-file.bin": string(largePayload),
37- },
38- })
39- if err != nil {
40- t.Fatalf("storage setup failed: %v", err)
41- }
42-
43- pubsub := NewPubsubChan()
44- defer func() {
45- _ = pubsub.Close()
46- }()
47-
48- cfg := NewPgsConfig(logger, dbpool, st, pubsub)
49- cfg.Domain = "pgs.test"
50- // Increase max asset size for testing large files
51- cfg.MaxAssetSize = 10 * 1024 * 1024 // 10MB
52-
53- // Set up the full stack WITH Souin HTTP caching middleware
54- httpCache := SetupCache(cfg)
55- routes := NewWebRouter(cfg)
56- cacher := &CachedHttp{
57- handler: httpCache,
58- routes: routes,
59- }
60-
61- // First request (cache miss)
62- req1 := httptest.NewRequest("GET", dbpool.mkpath("/large-file.bin"), strings.NewReader(""))
63- rec1 := httptest.NewRecorder()
64- cacher.ServeHTTP(rec1, req1)
65-
66- if rec1.Code != http.StatusOK {
67- t.Fatalf("first request failed with status %d", rec1.Code)
68- }
69-
70- body1 := rec1.Body.String()
71- size1 := len(body1)
72-
73- if size1 != expectedSize {
74- t.Errorf("first request: expected %d bytes, got %d bytes", expectedSize, size1)
75- }
76-
77- t.Logf("Cache miss: received %d bytes (expected %d)", size1, expectedSize)
78-
79- // Second request (cache hit) - This is where the bug manifests
80- req2 := httptest.NewRequest("GET", dbpool.mkpath("/large-file.bin"), strings.NewReader(""))
81- rec2 := httptest.NewRecorder()
82- cacher.ServeHTTP(rec2, req2)
83-
84- if rec2.Code != http.StatusOK {
85- t.Fatalf("second request failed with status %d", rec2.Code)
86- }
87-
88- body2 := rec2.Body.String()
89- size2 := len(body2)
90-
91- t.Logf("Cache hit: received %d bytes (expected %d)", size2, expectedSize)
92-
93- // CRITICAL ASSERTION: Both requests must return the full file
94- if size2 != expectedSize {
95- t.Errorf("SOUIN_TRUNCATION_BUG: cache hit returned %d bytes instead of %d bytes",
96- size2, expectedSize)
97-
98- // Show evidence of truncation
99- if size2 < 10000 {
100- t.Logf("Truncated response: only %d bytes (about %dKB)", size2, size2/1024)
101- }
102- }
103-
104- // Verify both responses are identical
105- if body1 != body2 {
106- t.Errorf("cache hit response differs from cache miss")
107- t.Errorf("Cache miss: %d bytes, Cache hit: %d bytes", size1, size2)
108- }
109-}
110-
111-// TestMediumFileNotTruncatedOnCacheHit tests with 512KB files
112-// which can trigger the race condition due to buffering behavior.
113-func TestMediumFileNotTruncatedOnCacheHit(t *testing.T) {
114- logger := slog.Default()
115- dbpool := NewPgsDb(logger)
116- bucketName := shared.GetAssetBucketName(dbpool.Users[0].ID)
117-
118- // 512KB payload with repeating pattern
119- payload := bytes.Repeat([]byte("0123456789ABCDEF"), 512*1024/16)
120- expectedSize := len(payload)
121-
122- st, err := storage.NewStorageMemory(map[string]map[string]string{
123- bucketName: {
124- "/test/medium-file.bin": string(payload),
125- },
126- })
127- if err != nil {
128- t.Fatalf("storage setup failed: %v", err)
129- }
130-
131- pubsub := NewPubsubChan()
132- defer func() {
133- _ = pubsub.Close()
134- }()
135-
136- cfg := NewPgsConfig(logger, dbpool, st, pubsub)
137- cfg.Domain = "pgs.test"
138- // Increase max asset size for testing large files
139- cfg.MaxAssetSize = 10 * 1024 * 1024 // 10MB
140-
141- httpCache := SetupCache(cfg)
142- routes := NewWebRouter(cfg)
143- cacher := &CachedHttp{
144- handler: httpCache,
145- routes: routes,
146- }
147-
148- // First request (cache miss)
149- req1 := httptest.NewRequest("GET", dbpool.mkpath("/medium-file.bin"), strings.NewReader(""))
150- rec1 := httptest.NewRecorder()
151- cacher.ServeHTTP(rec1, req1)
152-
153- body1 := rec1.Body.String()
154- size1 := len(body1)
155-
156- // Second request (cache hit)
157- req2 := httptest.NewRequest("GET", dbpool.mkpath("/medium-file.bin"), strings.NewReader(""))
158- rec2 := httptest.NewRecorder()
159- cacher.ServeHTTP(rec2, req2)
160-
161- body2 := rec2.Body.String()
162- size2 := len(body2)
163-
164- t.Logf("Cache miss: %d bytes, Cache hit: %d bytes (expected %d)", size1, size2, expectedSize)
165-
166- // Verify complete responses
167- if size1 != expectedSize {
168- t.Errorf("cache miss: expected %d bytes, got %d", expectedSize, size1)
169- }
170-
171- if size2 != expectedSize {
172- t.Errorf("SOUIN_TRUNCATION_BUG: cache hit returned %d bytes instead of %d bytes",
173- size2, expectedSize)
174- }
175-
176- // Verify content matches original
177- if body1 != string(payload) {
178- t.Errorf("cache miss response body doesn't match")
179- }
180-
181- if body2 != string(payload) {
182- t.Errorf("cache hit response body doesn't match")
183- }
184-}
+126,
-168
1@@ -8,7 +8,6 @@ import (
2 "html/template"
3 "log/slog"
4 "net/http"
5- "net/url"
6 "os"
7 "path/filepath"
8 "regexp"
9@@ -17,71 +16,116 @@ import (
10
11 _ "net/http/pprof"
12
13- "github.com/darkweak/souin/configurationtypes"
14- "github.com/darkweak/souin/pkg/middleware"
15- "github.com/darkweak/souin/plugins/souin/storages"
16- "github.com/darkweak/storages/core"
17 "github.com/gorilla/feeds"
18 "github.com/hashicorp/golang-lru/v2/expirable"
19 "github.com/picosh/pico/pkg/db"
20+ "github.com/picosh/pico/pkg/httpcache"
21 "github.com/picosh/pico/pkg/shared"
22 "github.com/picosh/pico/pkg/shared/router"
23 "github.com/picosh/pico/pkg/storage"
24+ "github.com/prometheus/client_golang/prometheus"
25+ "github.com/prometheus/client_golang/prometheus/promauto"
26 "github.com/prometheus/client_golang/prometheus/promhttp"
27- "google.golang.org/protobuf/proto"
28 )
29
30-type CachedHttp struct {
31- handler *middleware.SouinBaseHandler
32- routes *WebRouter
33+type PgsCacheKey struct {
34+ Domain string
35+ TxtPrefix string
36+}
37+
38+func (c *PgsCacheKey) GetCacheKey(r *http.Request) string {
39+ subdomain := router.GetSubdomainFromRequest(r, c.Domain, c.TxtPrefix)
40+ return subdomain + "__" + r.Method + "__" + r.URL.RequestURI()
41+}
42+
43+type PromCacheMetrics struct {
44+ Cache httpcache.Cacher
45+ CacheItems prometheus.Gauge
46+ CacheSizeBytes prometheus.Gauge
47+ CacheHit prometheus.Counter
48+ CacheMiss prometheus.Counter
49+ UpstreamReq prometheus.Counter
50+}
51+
52+func NewPromCacheMetrics(reg prometheus.Registerer) *PromCacheMetrics {
53+ name := "pgs"
54+ auto := promauto.With(reg)
55+ return &PromCacheMetrics{
56+ CacheItems: auto.NewGauge(prometheus.GaugeOpts{
57+ Namespace: name,
58+ Subsystem: "http_cache",
59+ Name: "total_items",
60+ Help: "Number of items in the http cache",
61+ }),
62+ CacheSizeBytes: auto.NewGauge(prometheus.GaugeOpts{
63+ Namespace: name,
64+ Subsystem: "http_cache",
65+ Name: "total_size",
66+ Help: "The total size of the http cache in bytes",
67+ }),
68+ CacheHit: auto.NewCounter(prometheus.CounterOpts{
69+ Namespace: name,
70+ Subsystem: "http_cache",
71+ Name: "cache_hit",
72+ Help: "The number of times there was a cache hit",
73+ }),
74+ CacheMiss: auto.NewCounter(prometheus.CounterOpts{
75+ Namespace: name,
76+ Subsystem: "http_cache",
77+ Name: "cache_miss",
78+ Help: "The number of times there was a cache miss",
79+ }),
80+ UpstreamReq: auto.NewCounter(prometheus.CounterOpts{
81+ Namespace: name,
82+ Subsystem: "http_cache",
83+ Name: "upstream_request",
84+ Help: "The number of times the upstream http server was requested",
85+ }),
86+ }
87 }
88-
89-func (c *CachedHttp) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
90- _ = c.handler.ServeHTTP(writer, req, func(w http.ResponseWriter, r *http.Request) error {
91- c.routes.ServeHTTP(w, r)
92- return nil
93- })
94+func (p *PromCacheMetrics) AddCacheItem(size float64) {
95+ p.CacheItems.Add(1)
96+ p.CacheSizeBytes.Add(size)
97+}
98+func (p *PromCacheMetrics) EvictCacheItem(key string, value []byte) {
99+ p.CacheItems.Add(-1)
100+ p.CacheSizeBytes.Add(-float64(len(value)))
101+}
102+func (p *PromCacheMetrics) AddCacheHit() {
103+ p.CacheHit.Add(1)
104+}
105+func (p *PromCacheMetrics) AddCacheMiss() {
106+ p.CacheMiss.Add(1)
107+}
108+func (p *PromCacheMetrics) AddUpstreamRequest() {
109+ p.UpstreamReq.Add(1)
110 }
111
112-func SetupCache(cfg *PgsConfig) *middleware.SouinBaseHandler {
113- ttl := configurationtypes.Duration{Duration: cfg.CacheTTL}
114- stale := configurationtypes.Duration{Duration: cfg.CacheTTL * 2}
115- c := &middleware.BaseConfiguration{
116- API: configurationtypes.API{
117- Prometheus: configurationtypes.APIEndpoint{
118- Enable: true,
119- },
120- },
121- DefaultCache: &configurationtypes.DefaultCache{
122- TTL: ttl,
123- Stale: stale,
124- Otter: configurationtypes.CacheProvider{
125- Uuid: fmt.Sprintf("OTTER-%s", stale),
126- Configuration: map[string]interface{}{},
127- },
128- Regex: configurationtypes.Regex{
129- Exclude: "/check|/_metrics",
130- },
131- MaxBodyBytes: uint64(cfg.MaxAssetSize),
132- DefaultCacheControl: cfg.CacheControl,
133+func NewPgsHttpCache(cfg *PgsConfig, upstream http.Handler) *httpcache.HttpCache {
134+ ttl := cfg.CacheTTL
135+ metrics := NewPromCacheMetrics(prometheus.DefaultRegisterer)
136+ cache := expirable.NewLRU(0, metrics.EvictCacheItem, ttl)
137+ httpCache := &httpcache.HttpCache{
138+ Ttl: ttl,
139+ Logger: cfg.Logger,
140+ Upstream: upstream,
141+ Cache: cache,
142+ CacheKey: &PgsCacheKey{
143+ Domain: cfg.Domain,
144+ TxtPrefix: cfg.TxtPrefix,
145 },
146+ CacheMetrics: metrics,
147 }
148- c.SetLogger(&CompatLogger{Logger: cfg.Logger})
149- storages.InitFromConfiguration(c)
150- return middleware.NewHTTPCacheHandler(c)
151+ httpCache.Logger.Info("httpcache initiated", "ttl", httpCache.Ttl, "storage", "lru")
152+ return httpCache
153 }
154
155 func StartApiServer(cfg *PgsConfig) {
156 ctx := context.Background()
157
158- httpCache := SetupCache(cfg)
159- routes := NewWebRouter(cfg)
160- cacher := &CachedHttp{
161- handler: httpCache,
162- routes: routes,
163- }
164-
165- go routes.CacheMgmt(ctx, httpCache, cfg.CacheClearingQueue)
166+ router := NewWebRouter(cfg)
167+ httpCache := NewPgsHttpCache(router.Cfg, router)
168+ go CacheMgmt(ctx, cfg.CacheClearingQueue, cfg, httpCache.Cache)
169
170 portStr := fmt.Sprintf(":%s", cfg.WebPort)
171 cfg.Logger.Info(
172@@ -89,7 +133,7 @@ func StartApiServer(cfg *PgsConfig) {
173 "port", cfg.WebPort,
174 "domain", cfg.Domain,
175 )
176- err := http.ListenAndServe(portStr, cacher)
177+ err := http.ListenAndServe(portStr, httpCache)
178 cfg.Logger.Error(
179 "listen and serve",
180 "err", err.Error(),
181@@ -122,6 +166,26 @@ func newWebRouter(cfg *PgsConfig) *WebRouter {
182 return router
183 }
184
185+func (web *WebRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
186+ subdomain := router.GetSubdomainFromRequest(r, web.Cfg.Domain, web.Cfg.TxtPrefix)
187+ if web.RootRouter == nil || web.UserRouter == nil {
188+ web.Cfg.Logger.Error("routers not initialized")
189+ http.Error(w, "routers not initialized", http.StatusInternalServerError)
190+ return
191+ }
192+
193+ var mux *http.ServeMux
194+ if subdomain == "" {
195+ mux = web.RootRouter
196+ } else {
197+ mux = web.UserRouter
198+ }
199+
200+ ctx := r.Context()
201+ ctx = context.WithValue(ctx, router.CtxSubdomainKey{}, subdomain)
202+ mux.ServeHTTP(w, r.WithContext(ctx))
203+}
204+
205 func (web *WebRouter) WatchCacheClear() {
206 for key := range web.Cfg.CacheClearingQueue {
207 web.Cfg.Logger.Info("lru cache clear request", "key", key)
208@@ -288,56 +352,27 @@ func (web *WebRouter) checkHandler(w http.ResponseWriter, r *http.Request) {
209 w.WriteHeader(http.StatusNotFound)
210 }
211
212-func (web *WebRouter) CacheMgmt(ctx context.Context, httpCache *middleware.SouinBaseHandler, notify chan string) {
213- storer := httpCache.Storers[0]
214-
215+func CacheMgmt(ctx context.Context, notify chan string, cfg *PgsConfig, cacher httpcache.Cacher) {
216+ cfg.Logger.Info("cache mgmt initiated")
217 for {
218- scanner := bufio.NewScanner(web.Cfg.Pubsub)
219+ scanner := bufio.NewScanner(cfg.Pubsub)
220 scanner.Buffer(make([]byte, 32*1024), 32*1024)
221 for scanner.Scan() {
222- surrogateKey := strings.TrimSpace(scanner.Text())
223- web.Cfg.Logger.Info("received cache-drain item", "surrogateKey", surrogateKey)
224- notify <- surrogateKey
225-
226- if surrogateKey == "*" {
227- storer.DeleteMany(".+")
228- err := httpCache.SurrogateKeyStorer.Destruct()
229- if err != nil {
230- web.Cfg.Logger.Error("could not clear cache and surrogate key store", "err", err)
231- } else {
232- web.Cfg.Logger.Info("successfully cleared cache and surrogate keys store")
233- }
234+ subdomain := strings.TrimSpace(scanner.Text())
235+ cfg.Logger.Info("received cache-drain item", "subdomain", subdomain)
236+ notify <- subdomain
237+
238+ if subdomain == "*" {
239+ cacher.Purge()
240+ cfg.Logger.Info("successfully cleared cache from remote cli request")
241 continue
242 }
243
244- var header http.Header = map[string][]string{}
245- header.Add("Surrogate-Key", surrogateKey)
246-
247- ck, _ := httpCache.SurrogateKeyStorer.Purge(header)
248- for _, key := range ck {
249- key, _ = strings.CutPrefix(key, core.MappingKeyPrefix)
250- if b := storer.Get(core.MappingKeyPrefix + key); len(b) > 0 {
251- var mapping core.StorageMapper
252- if e := proto.Unmarshal(b, &mapping); e == nil {
253- for k := range mapping.GetMapping() {
254- qkey, _ := url.QueryUnescape(k)
255- web.Cfg.Logger.Info(
256- "deleting key from surrogate cache",
257- "surrogateKey", surrogateKey,
258- "key", qkey,
259- )
260- storer.Delete(qkey)
261- }
262- }
263+ for _, key := range cacher.Keys() {
264+ if strings.HasPrefix(key, subdomain) {
265+ cfg.Logger.Info("deleting cache item", "subdomain", subdomain, "key", key)
266+ _ = cacher.Remove(key)
267 }
268-
269- qkey, _ := url.QueryUnescape(key)
270- web.Cfg.Logger.Info(
271- "deleting from cache",
272- "surrogateKey", surrogateKey,
273- "key", core.MappingKeyPrefix+qkey,
274- )
275- storer.Delete(core.MappingKeyPrefix + qkey)
276 }
277 }
278 }
279@@ -573,80 +608,3 @@ func (web *WebRouter) handleLogin(w http.ResponseWriter, r *http.Request) {
280 func (web *WebRouter) handleAutoForm(w http.ResponseWriter, r *http.Request) {
281 handleAutoForm(w, r, web.Cfg)
282 }
283-
284-func (web *WebRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
285- subdomain := router.GetSubdomainFromRequest(r, web.Cfg.Domain, web.Cfg.TxtPrefix)
286- if web.RootRouter == nil || web.UserRouter == nil {
287- web.Cfg.Logger.Error("routers not initialized")
288- http.Error(w, "routers not initialized", http.StatusInternalServerError)
289- return
290- }
291-
292- var mux *http.ServeMux
293- if subdomain == "" {
294- mux = web.RootRouter
295- } else {
296- mux = web.UserRouter
297- }
298-
299- ctx := r.Context()
300- ctx = context.WithValue(ctx, router.CtxSubdomainKey{}, subdomain)
301- mux.ServeHTTP(w, r.WithContext(ctx))
302-}
303-
304-type CompatLogger struct {
305- Logger *slog.Logger
306-}
307-
308-func (cl *CompatLogger) marshall(int ...interface{}) string {
309- res := ""
310- for _, val := range int {
311- switch r := val.(type) {
312- case string:
313- res += " " + r
314- }
315- }
316- return res
317-}
318-func (cl *CompatLogger) DPanic(int ...interface{}) {
319- cl.Logger.Error("panic", "output", cl.marshall(int))
320-}
321-func (cl *CompatLogger) DPanicf(st string, int ...interface{}) {
322- cl.Logger.Error(fmt.Sprintf(st, int...))
323-}
324-func (cl *CompatLogger) Debug(int ...interface{}) {
325- cl.Logger.Debug("debug", "output", cl.marshall(int))
326-}
327-func (cl *CompatLogger) Debugf(st string, int ...interface{}) {
328- cl.Logger.Debug(fmt.Sprintf(st, int...))
329-}
330-func (cl *CompatLogger) Error(int ...interface{}) {
331- cl.Logger.Error("error", "output", cl.marshall(int))
332-}
333-func (cl *CompatLogger) Errorf(st string, int ...interface{}) {
334- cl.Logger.Error(fmt.Sprintf(st, int...))
335-}
336-func (cl *CompatLogger) Fatal(int ...interface{}) {
337- cl.Logger.Error("fatal", "outpu", cl.marshall(int))
338-}
339-func (cl *CompatLogger) Fatalf(st string, int ...interface{}) {
340- cl.Logger.Error(fmt.Sprintf(st, int...))
341-}
342-func (cl *CompatLogger) Info(int ...interface{}) {
343- cl.Logger.Info("info", "output", cl.marshall(int))
344-}
345-func (cl *CompatLogger) Infof(st string, int ...interface{}) {
346- cl.Logger.Info(fmt.Sprintf(st, int...))
347-}
348-func (cl *CompatLogger) Panic(int ...interface{}) {
349- cl.Logger.Error("panic", "output", cl.marshall(int))
350-}
351-func (cl *CompatLogger) Panicf(st string, int ...interface{}) {
352- cl.Logger.Error(fmt.Sprintf(st, int...))
353-}
354-func (cl *CompatLogger) Warn(int ...interface{}) {
355- cl.Logger.Warn("warn", "output", cl.marshall(int))
356-}
357-func (cl *CompatLogger) Warnf(st string, int ...interface{}) {
358- cl.Logger.Warn(fmt.Sprintf(st, int...))
359-}
+1,
-0
1@@ -289,6 +289,7 @@ func (h *ApiAssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2 )
3 done, _ := checkPreconditions(w, r, info.LastModified.UTC())
4 if done {
5+ logger.Info("A conditaionl request was detected, no body required")
6 // A conditional request was detected, status and headers are set, no body required (either 412 or 304)
7 return
8 }
+930,
-0
1@@ -0,0 +1,930 @@
2+package httpcache
3+
4+import (
5+ "encoding/json"
6+ "log/slog"
7+ "net/http"
8+ "net/http/httptest"
9+ "strconv"
10+ "strings"
11+ "testing"
12+ "time"
13+)
14+
15+/*
16+TODO:
17+ - Request no-store store-prevention (RFC 9111 §5.2.1.5): verify a no-store request does not populate/update cache for subsequent requests.
18+ - Authorization storage/use constraints (RFC 9111 §3.5): authenticated responses should not be reused unless explicitly permitted by response directives.
19+ - Vary: * behavior (RFC 9111 §4.1): ensure such responses are not reused for subsequent requests.
20+ - Multi-field Vary matching (RFC 9111 §4.1): all nominated request fields must match original request values, not just one.
21+ - Age correction with upstream metadata (RFC 9111 §4.2.3, §5.1): test interactions of stored response Date/Age values rather than only local clock delta.
22+*/
23+
24+// TestContext holds shared test state.
25+type TestContext struct {
26+ t *testing.T
27+ handler http.Handler
28+ cachedServer *httptest.Server
29+}
30+
31+// NewTestContext creates a test context with a backend and cached server.
32+func NewTestContext(t *testing.T, cacheHandler http.Handler) *TestContext {
33+ tc := &TestContext{
34+ t: t,
35+ handler: cacheHandler,
36+ }
37+
38+ tc.cachedServer = httptest.NewServer(tc.handler)
39+ t.Cleanup(tc.cachedServer.Close)
40+
41+ return tc
42+}
43+
44+func (tc *TestContext) Do(req *http.Request) (*http.Response, error) {
45+ return http.DefaultClient.Do(req)
46+}
47+
48+func (tc *TestContext) DoWithHeaders(req *http.Request, headers map[string][]string) (*http.Response, error) {
49+ reqCopy := req.Clone(req.Context())
50+ reqCopy.Header = req.Header.Clone()
51+ if reqCopy.Header == nil {
52+ reqCopy.Header = make(http.Header)
53+ }
54+
55+ for key, val := range headers {
56+ reqCopy.Header.Del(key)
57+ for _, v := range val {
58+ reqCopy.Header.Add(key, v)
59+ }
60+ }
61+ return http.DefaultClient.Do(reqCopy)
62+}
63+
64+func (tc *TestContext) GetHeader(resp *http.Response, key string) string {
65+ return resp.Header.Get(key)
66+}
67+
68+func testCacheValue(afterCreated time.Duration) *CacheValue {
69+ return &CacheValue{
70+ Header: map[string][]string{},
71+ Body: []byte("success"),
72+ CreatedAt: time.Now().Add(-afterCreated),
73+ }
74+}
75+
76+// RFC 9211 The Cache-Status HTTP Response Header Field
77+// https://www.rfc-editor.org/rfc/rfc9211#section-2
78+func TestCacheCacheStatus(t *testing.T) {
79+ mux := http.NewServeMux()
80+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
81+ w.WriteHeader(200)
82+ _, _ = w.Write([]byte("success"))
83+ })
84+
85+ logger := slog.Default()
86+ handler := NewHttpCache(logger, mux)
87+ tc := NewTestContext(t, handler)
88+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
89+
90+ // first request hits backend
91+ resp1, _ := tc.Do(req)
92+ if resp1.StatusCode != http.StatusOK {
93+ t.Errorf("expected 200, got %d", resp1.StatusCode)
94+ }
95+ status := resp1.Header.Get("cache-status")
96+ if !strings.Contains(status, "miss") {
97+ t.Errorf("expected miss, got %s", status)
98+ }
99+
100+ // second request hits cache
101+ resp2, _ := tc.Do(req)
102+ if resp2.StatusCode != http.StatusOK {
103+ t.Errorf("expected 200, got %d", resp2.StatusCode)
104+ }
105+ status = resp2.Header.Get("cache-status")
106+ if !strings.Contains(status, "hit") {
107+ t.Errorf("expected hit, got %s", status)
108+ }
109+}
110+
111+// RFC 9110 15.1-2 Heuristically Cacheable
112+// https://www.rfc-editor.org/rfc/rfc9110#section-15.1-2
113+// 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, and 501.
114+func TestCacheStatusCode(t *testing.T) {
115+ mux := http.NewServeMux()
116+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
117+ w.WriteHeader(500)
118+ _, _ = w.Write([]byte("boom!"))
119+ })
120+
121+ logger := slog.Default()
122+ handler := NewHttpCache(logger, mux)
123+ tc := NewTestContext(t, handler)
124+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
125+
126+ // first request hits backend
127+ resp1, _ := tc.Do(req)
128+ if resp1.StatusCode != http.StatusInternalServerError {
129+ t.Errorf("expected 500, got %d", resp1.StatusCode)
130+ }
131+ status := resp1.Header.Get("cache-status")
132+ if !strings.Contains(status, "miss") {
133+ t.Errorf("expected miss, got %s", status)
134+ }
135+
136+ // second request hits backend
137+ resp2, _ := tc.Do(req)
138+ if resp2.StatusCode != http.StatusInternalServerError {
139+ t.Errorf("expected 500, got %d", resp2.StatusCode)
140+ }
141+ status = resp2.Header.Get("cache-status")
142+ if !strings.Contains(status, "miss") {
143+ t.Errorf("expected miss, got %s", status)
144+ }
145+}
146+
147+// RFC 9111 2.3 Opinion - Only store GET requests
148+// https://www.rfc-editor.org/rfc/rfc9111.html#section-2-3
149+func TestCacheMethod(t *testing.T) {
150+ mux := http.NewServeMux()
151+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
152+ w.WriteHeader(200)
153+ _, _ = w.Write([]byte("success"))
154+ })
155+
156+ logger := slog.Default()
157+ handler := NewHttpCache(logger, mux)
158+ tc := NewTestContext(t, handler)
159+ req, _ := http.NewRequest("POST", tc.cachedServer.URL+"/test", nil)
160+
161+ // first request hits backend
162+ resp1, _ := tc.Do(req)
163+ if resp1.StatusCode != http.StatusOK {
164+ t.Errorf("expected 200, got %d", resp1.StatusCode)
165+ }
166+ status := resp1.Header.Get("cache-status")
167+ if !strings.Contains(status, "miss") {
168+ t.Errorf("expected miss, got %s", status)
169+ }
170+
171+ // second request hits backend
172+ resp2, _ := tc.Do(req)
173+ if resp2.StatusCode != http.StatusOK {
174+ t.Errorf("expected 200, got %d", resp2.StatusCode)
175+ }
176+ status = resp2.Header.Get("cache-status")
177+ if !strings.Contains(status, "miss") {
178+ t.Errorf("expected miss, got %s", status)
179+ }
180+}
181+
182+// RFC 9111 3.1 Storing Header and Trailer Fields
183+// https://www.rfc-editor.org/rfc/rfc9111.html#section-3.1
184+func TestCacheStoringHeaders(t *testing.T) {
185+ mux := http.NewServeMux()
186+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
187+ w.Header().Set("connection", "idk")
188+ w.Header().Set("proxy-authenticate", "idk")
189+ w.Header().Set("proxy-authentication-info", "idk")
190+ w.Header().Set("proxy-authorization", "idk")
191+
192+ w.WriteHeader(200)
193+ _, _ = w.Write([]byte("success"))
194+ })
195+
196+ logger := slog.Default()
197+ handler := NewHttpCache(logger, mux)
198+ tc := NewTestContext(t, handler)
199+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
200+
201+ // first request hits backend
202+ resp1, _ := tc.Do(req)
203+ if resp1.StatusCode != http.StatusOK {
204+ t.Errorf("expected 200, got %d", resp1.StatusCode)
205+ }
206+ status := resp1.Header.Get("cache-status")
207+ if !strings.Contains(status, "miss") {
208+ t.Errorf("expected miss, got %s", status)
209+ }
210+
211+ // second request hits cache
212+ resp2, _ := tc.Do(req)
213+ if resp2.StatusCode != http.StatusOK {
214+ t.Errorf("expected 200, got %d", resp2.StatusCode)
215+ }
216+ headers := []string{
217+ resp2.Header.Get("connection"),
218+ resp2.Header.Get("proxy-authenticate"),
219+ resp2.Header.Get("proxy-authentication-info"),
220+ resp2.Header.Get("proxy-authorization"),
221+ }
222+ for _, hdr := range headers {
223+ if hdr != "" {
224+ t.Errorf("expected no header, found one: %s", hdr)
225+ }
226+ }
227+}
228+
229+// RFC 9111 4.1 Vary.
230+// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1
231+func TestCacheVary(t *testing.T) {
232+ mux := http.NewServeMux()
233+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
234+ w.WriteHeader(200)
235+ _, _ = w.Write([]byte("success"))
236+ })
237+
238+ logger := slog.Default()
239+ handler := NewHttpCache(logger, mux)
240+ tc := NewTestContext(t, handler)
241+
242+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
243+ cacheKey := handler.GetCacheKey(req)
244+ cv := testCacheValue(250 * time.Second)
245+ cv.Header["Vary"] = []string{"Accept-Encoding"}
246+ // Store the original request header that selected this representation.
247+ cv.Header["Accept-Encoding"] = []string{"gzip"}
248+ cacheValue, _ := json.Marshal(cv)
249+ handler.Cache.Add(cacheKey, cacheValue)
250+
251+ respMatch, _ := tc.DoWithHeaders(req, map[string][]string{
252+ "Accept-Encoding": {"gzip"},
253+ })
254+ status := respMatch.Header.Get("cache-status")
255+ if !strings.Contains(status, "hit") {
256+ t.Errorf("expected hit, got %s", status)
257+ }
258+
259+ respMisMatch, _ := tc.DoWithHeaders(req, map[string][]string{
260+ "Accept-Encoding": {"text/plain"},
261+ })
262+ status = respMisMatch.Header.Get("cache-status")
263+ if !strings.Contains(status, "miss") {
264+ t.Errorf("expected miss, got %s", status)
265+ }
266+}
267+
268+// RFC 9111 4.3 Validation.
269+// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.3
270+// Last-Modified ETag If-Match If-None-Match If-Range If-Modified-Since If-Unmodified-Since
271+// RFC 9110 13 Conditional Requests.
272+// https://www.rfc-editor.org/rfc/rfc9110#section-13
273+func TestCacheValidation(t *testing.T) {
274+ actual := time.Now().Add(-10 * time.Minute).UTC()
275+ actualStr := actual.Format(time.RFC1123)
276+ now := time.Now().UTC()
277+ nowStr := now.Format(time.RFC1123)
278+ early := time.Now().Add(-20 * time.Minute)
279+ earlyStr := early.Format(time.RFC1123)
280+ tests := []struct {
281+ name string
282+ link string
283+ validationHeader string
284+ validationValue string
285+ extraHeaders map[string][]string
286+ expected string
287+ originStatus int
288+ expectedStatus int
289+ }{
290+ {
291+ name: "RFC 9110 13.1.2 If-None-Match",
292+ link: "https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2",
293+ validationHeader: "If-None-Match",
294+ validationValue: "\"abc\"",
295+ expected: "hit",
296+ originStatus: http.StatusOK,
297+ expectedStatus: http.StatusOK,
298+ },
299+ {
300+ name: "RFC 9110 13.1.2 If-None-Match Wildcard",
301+ link: "https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2",
302+ validationHeader: "If-None-Match",
303+ validationValue: "*",
304+ expected: "hit",
305+ originStatus: http.StatusOK,
306+ expectedStatus: http.StatusNotModified,
307+ },
308+ {
309+ name: "RFC 9110 13.1.3 If-Modified-Since",
310+ link: "https://www.rfc-editor.org/rfc/rfc9110#section-13.1.3",
311+ validationHeader: "If-Modified-Since",
312+ validationValue: nowStr,
313+ expected: "hit",
314+ originStatus: http.StatusOK,
315+ expectedStatus: http.StatusNotModified,
316+ },
317+ {
318+ name: "RFC 9110 13.1.4 If-Unmodified-Since",
319+ link: "https://www.rfc-editor.org/rfc/rfc9110#section-13.1.4",
320+ validationHeader: "If-Unmodified-Since",
321+ validationValue: earlyStr,
322+ expected: "hit",
323+ originStatus: http.StatusOK,
324+ expectedStatus: http.StatusOK,
325+ },
326+ {
327+ name: "RFC 9110 13.1.5 If-Range Date",
328+ link: "https://www.rfc-editor.org/rfc/rfc9110#section-13.1.5",
329+ validationHeader: "If-Range",
330+ validationValue: nowStr,
331+ extraHeaders: map[string][]string{
332+ "Range": {"bytes=0-3"},
333+ },
334+ expected: "hit",
335+ originStatus: http.StatusOK,
336+ expectedStatus: http.StatusOK,
337+ },
338+ {
339+ name: "RFC 9110 13.1.5 If-Range Date Hit",
340+ link: "https://www.rfc-editor.org/rfc/rfc9110#section-13.1.5",
341+ validationHeader: "If-Range",
342+ validationValue: actualStr,
343+ extraHeaders: map[string][]string{
344+ "Range": {"bytes=0-3"},
345+ },
346+ expected: "hit",
347+ originStatus: http.StatusOK,
348+ expectedStatus: http.StatusOK,
349+ },
350+ {
351+ name: "RFC 9110 13.1.5 If-Range ETag",
352+ link: "https://www.rfc-editor.org/rfc/rfc9110#section-13.1.5",
353+ validationHeader: "If-Range",
354+ validationValue: "\"abc\"",
355+ extraHeaders: map[string][]string{
356+ "Range": {"bytes=0-3"},
357+ },
358+ expected: "hit",
359+ originStatus: http.StatusOK,
360+ expectedStatus: http.StatusOK,
361+ },
362+ {
363+ name: "RFC 9110 13.1.5 If-Range ETag Hit",
364+ link: "https://www.rfc-editor.org/rfc/rfc9110#section-13.1.5",
365+ validationHeader: "If-Range",
366+ validationValue: "\"ccc\"",
367+ extraHeaders: map[string][]string{
368+ "Range": {"bytes=0-3"},
369+ },
370+ expected: "hit",
371+ originStatus: http.StatusOK,
372+ expectedStatus: http.StatusOK,
373+ },
374+ }
375+
376+ for _, tt := range tests {
377+ mux := http.NewServeMux()
378+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
379+ w.WriteHeader(tt.originStatus)
380+ })
381+
382+ logger := slog.Default()
383+ handler := NewHttpCache(logger, mux)
384+ handler.Ttl = time.Minute * 10
385+ tc := NewTestContext(t, handler)
386+
387+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
388+ cacheKey := handler.GetCacheKey(req)
389+ cv := testCacheValue(250 * time.Second)
390+ cv.Header["ETag"] = []string{"ccc"}
391+ cv.Header["Last-Modified"] = []string{actualStr}
392+ cacheValue, _ := json.Marshal(cv)
393+ handler.Cache.Add(cacheKey, cacheValue)
394+
395+ t.Run(tt.name, func(t *testing.T) {
396+ reqHeaders := map[string][]string{tt.validationHeader: {tt.validationValue}}
397+ for key, values := range tt.extraHeaders {
398+ reqHeaders[key] = values
399+ }
400+
401+ resp, _ := tc.DoWithHeaders(req, reqHeaders)
402+ actual := resp.Header.Get("cache-status")
403+ if !strings.Contains(actual, tt.expected) {
404+ t.Errorf("expected %s, got %s\n%s", tt.expected, actual, tt.link)
405+ }
406+ if resp.StatusCode != tt.expectedStatus {
407+ t.Errorf("expected status %d, got %d", tt.expectedStatus, resp.StatusCode)
408+ }
409+ })
410+ }
411+}
412+
413+// RFC 9111 5.1 Age.
414+// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.1
415+// RFC 9111 4.2.3 Calculating Age.
416+// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.2.3
417+func TestCacheAge(t *testing.T) {
418+ mux := http.NewServeMux()
419+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
420+ w.WriteHeader(200)
421+ _, _ = w.Write([]byte("success"))
422+ })
423+
424+ logger := slog.Default()
425+ handler := NewHttpCache(logger, mux)
426+ tc := NewTestContext(t, handler)
427+
428+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
429+ cacheKey := handler.GetCacheKey(req)
430+ cacheValue, _ := json.Marshal(testCacheValue(250 * time.Second))
431+ handler.Cache.Add(cacheKey, cacheValue)
432+
433+ resp, _ := tc.Do(req)
434+ if resp.StatusCode != http.StatusOK {
435+ t.Errorf("expected 200, got %d", resp.StatusCode)
436+ }
437+ age := resp.Header.Get("age")
438+ ageNum, err := strconv.Atoi(age)
439+ if err != nil {
440+ t.Fatalf("invalide age header %s", err)
441+ }
442+ if ageNum != 251 {
443+ t.Errorf("expected 250, got %d", ageNum)
444+ }
445+}
446+
447+// RFC 9111 5.2.1 Request Directives
448+// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1
449+func TestCacheRequestDirectives(t *testing.T) {
450+ tests := []struct {
451+ name string
452+ link string
453+ cacheControl string
454+ expected string
455+ }{
456+ {
457+ name: "RFC 9111 5.2.1.1 Request Cache-Control: max-age",
458+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1",
459+ cacheControl: "max-age=100",
460+ expected: "miss",
461+ },
462+ {
463+ name: "RFC 9111 5.2.1.2 Request Cache-Control: max-stale",
464+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.2",
465+ cacheControl: "max-stale=300", // 300 max-stale + 250 age = 550 > 450 freshness
466+ expected: "miss",
467+ },
468+ {
469+ name: "RFC 9111 5.2.1.3 Request Cache-Control: min-fresh",
470+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.3",
471+ cacheControl: "min-fresh=400", // 600 ttl - 250 age = 350 freshness
472+ expected: "miss",
473+ },
474+ {
475+ name: "RFC 9111 5.2.1.4 Request Cache-Control: no-cache",
476+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.4",
477+ cacheControl: "no-cache",
478+ expected: "miss",
479+ },
480+ {
481+ name: "RFC 9111 5.2.1.5 Request Cache-Control: no-store",
482+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.5",
483+ cacheControl: "no-store",
484+ // you can reply with the cached version, just cannot store or update the response in
485+ // the cache.
486+ expected: "hit",
487+ },
488+ {
489+ name: "RFC 9111 5.2.1.6 Request Cache-Control: no-transform",
490+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.6",
491+ cacheControl: "no-transform",
492+ expected: "miss",
493+ },
494+ {
495+ name: "RFC 9111 5.2.1.7 Request Cache-Control: only-if-cached",
496+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.7",
497+ cacheControl: "only-if-cached",
498+ expected: "hit",
499+ },
500+ }
501+
502+ for _, tt := range tests {
503+ mux := http.NewServeMux()
504+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
505+ w.WriteHeader(200)
506+ _, _ = w.Write([]byte("success"))
507+ })
508+
509+ logger := slog.Default()
510+ handler := NewHttpCache(logger, mux)
511+ handler.Ttl = time.Minute * 10
512+ tc := NewTestContext(t, handler)
513+
514+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
515+ cacheKey := handler.GetCacheKey(req)
516+ cacheValue, _ := json.Marshal(testCacheValue(250 * time.Second))
517+ handler.Cache.Add(cacheKey, cacheValue)
518+
519+ t.Run(tt.name, func(t *testing.T) {
520+ resp, _ := tc.DoWithHeaders(req, map[string][]string{"Cache-Control": {tt.cacheControl}})
521+ actual := resp.Header.Get("cache-status")
522+ if !strings.Contains(actual, tt.expected) {
523+ t.Errorf("expected %s, got %s\n%s", tt.expected, actual, tt.link)
524+ }
525+ })
526+ }
527+}
528+
529+// RFC 9111 5.2.2 Response Directives
530+// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2
531+// These tests simply confirm that the response generated from origin server corresponds to the
532+// correct cache-control, http status code, and is "revalidated" the correct number of times.
533+// It does **not** validate the correct cache control logic like cache using max-age from origin
534+// server.
535+func TestCacheResponseDirectivesHasCacheControl(t *testing.T) {
536+ tests := []struct {
537+ name string
538+ link string
539+ cacheControl string
540+ expectedOriginCalls int
541+ expectedSecondCacheStatus string
542+ }{
543+ {
544+ name: "RFC 9111 5.2.2.1 Response Cache-Control max-age",
545+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.1",
546+ cacheControl: "max-age=100",
547+ expectedOriginCalls: 1,
548+ expectedSecondCacheStatus: "hit",
549+ },
550+ {
551+ name: "RFC 9111 5.2.2.2 Response Cache-Control must-revalidate",
552+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.2",
553+ cacheControl: "must-revalidate",
554+ expectedOriginCalls: 1,
555+ expectedSecondCacheStatus: "hit",
556+ },
557+ {
558+ name: "RFC 9111 5.2.2.3 Response Cache-Control must-understand",
559+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.3",
560+ cacheControl: "must-understand",
561+ expectedOriginCalls: 1,
562+ expectedSecondCacheStatus: "hit",
563+ },
564+ {
565+ name: "RFC 9111 5.2.2.4 Response Cache-Control no-cache",
566+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.4",
567+ cacheControl: "no-cache",
568+ expectedOriginCalls: 2,
569+ expectedSecondCacheStatus: "miss",
570+ },
571+ {
572+ name: "RFC 9111 5.2.2.5 Response Cache-Control no-store",
573+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.5",
574+ cacheControl: "no-store",
575+ expectedOriginCalls: 2,
576+ expectedSecondCacheStatus: "miss",
577+ },
578+ {
579+ name: "RFC 9111 5.2.2.6 Response Cache-Control no-transform",
580+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.6",
581+ cacheControl: "no-transform",
582+ expectedOriginCalls: 1,
583+ expectedSecondCacheStatus: "hit",
584+ },
585+ {
586+ name: "RFC 9111 5.2.2.7 Response Cache-Control private",
587+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.7",
588+ cacheControl: "private",
589+ expectedOriginCalls: 2,
590+ expectedSecondCacheStatus: "miss", // this is a shared cache, do not store private
591+ },
592+ {
593+ name: "RFC 9111 5.2.2.8 Response Cache-Control proxy-revalidate",
594+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.8",
595+ cacheControl: "proxy-revalidate",
596+ expectedOriginCalls: 1,
597+ expectedSecondCacheStatus: "hit",
598+ },
599+ {
600+ name: "RFC 9111 5.2.2.9 Response Cache-Control public",
601+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.9",
602+ cacheControl: "public",
603+ expectedOriginCalls: 1,
604+ expectedSecondCacheStatus: "hit",
605+ },
606+ {
607+ name: "RFC 9111 5.2.2.10 Response Cache-Control s-maxage",
608+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10",
609+ cacheControl: "s-maxage=100",
610+ expectedOriginCalls: 1,
611+ expectedSecondCacheStatus: "hit",
612+ },
613+ {
614+ name: "RFC 9111 5.2.2.10 Response Cache-Control public+private",
615+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10",
616+ cacheControl: "public, s-maxage=100, private",
617+ expectedOriginCalls: 2,
618+ expectedSecondCacheStatus: "miss", // be restrictive and adhere to private directive
619+ },
620+ }
621+
622+ for _, tt := range tests {
623+ t.Run(tt.name, func(t *testing.T) {
624+ mux := http.NewServeMux()
625+ actualOriginCalls := 0
626+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
627+ actualOriginCalls += 1
628+ w.Header().Set("cache-control", tt.cacheControl)
629+ w.WriteHeader(200)
630+ _, _ = w.Write([]byte("success"))
631+ })
632+
633+ logger := slog.Default()
634+ handler := NewHttpCache(logger, mux)
635+ handler.Ttl = time.Minute * 10
636+ tc := NewTestContext(t, handler)
637+
638+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
639+
640+ // first request hits backend
641+ resp1, _ := tc.Do(req)
642+ status := resp1.Header.Get("cache-status")
643+ if !strings.Contains(status, "miss") {
644+ t.Errorf("expected miss, got %s", status)
645+ }
646+
647+ // second request can be served from cache or forwarded depending on directive
648+ resp2, _ := tc.Do(req)
649+
650+ actualCc := resp2.Header.Get("cache-control")
651+ if actualCc != tt.cacheControl {
652+ t.Errorf("expected cache-control %s, got %s", tt.cacheControl, actualCc)
653+ }
654+ status = resp2.Header.Get("cache-status")
655+ if tt.expectedSecondCacheStatus != "" && !strings.Contains(status, tt.expectedSecondCacheStatus) {
656+ t.Errorf("expected %s, got %s", tt.expectedSecondCacheStatus, status)
657+ }
658+ if tt.expectedOriginCalls != actualOriginCalls {
659+ t.Errorf("expected %d origin calls, got %d", tt.expectedOriginCalls, actualOriginCalls)
660+ }
661+ })
662+ }
663+}
664+
665+// RFC 9111 5.3 Expires
666+// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
667+func TestCacheExpires(t *testing.T) {
668+ tests := []struct {
669+ name string
670+ link string
671+ expires string
672+ expectedStatus int
673+ expectedCacheStatus string
674+ }{
675+ {
676+ name: "RFC 9111 5.3 Expires - future date",
677+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3",
678+ expires: time.Now().Add(10 * time.Minute).UTC().Format(http.TimeFormat),
679+ expectedStatus: http.StatusOK,
680+ expectedCacheStatus: "hit",
681+ },
682+ {
683+ name: "RFC 9111 5.3 Expires - expired response",
684+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3",
685+ expires: time.Now().Add(-10 * time.Minute).UTC().Format(http.TimeFormat),
686+ expectedStatus: http.StatusOK,
687+ expectedCacheStatus: "fwd=uri-miss",
688+ },
689+ {
690+ name: "RFC 9111 5.3 Expires - invalid Expires header",
691+ link: "https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3",
692+ expires: "not-a-valid-date",
693+ expectedStatus: http.StatusOK,
694+ expectedCacheStatus: "fwd=uri-miss",
695+ },
696+ }
697+
698+ for _, tt := range tests {
699+ t.Run(tt.name, func(t *testing.T) {
700+ mux := http.NewServeMux()
701+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
702+ w.Header().Set("Expires", tt.expires)
703+ w.WriteHeader(200)
704+ _, _ = w.Write([]byte("success"))
705+ })
706+
707+ logger := slog.Default()
708+ handler := NewHttpCache(logger, mux)
709+ tc := NewTestContext(t, handler)
710+
711+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
712+
713+ // first request hits backend
714+ resp1, _ := tc.Do(req)
715+ if resp1.StatusCode != http.StatusOK {
716+ t.Errorf("expected 200, got %d", resp1.StatusCode)
717+ }
718+
719+ // second request behavior depends on Expires header
720+ resp2, _ := tc.Do(req)
721+ status := resp2.Header.Get("cache-status")
722+ if !strings.Contains(status, tt.expectedCacheStatus) {
723+ t.Errorf("expected %s, got %s\n%s", tt.expectedCacheStatus, status, tt.link)
724+ }
725+ })
726+ }
727+}
728+
729+// RFC 9111 4.3.4 304 Not Modified
730+// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.3.4
731+// When a cached entry is validated and the origin responds with 304, the cache:
732+// - Returns 304 to the client
733+// - Updates header metadata from the 304 response
734+// - Retains the cached body for subsequent requests.
735+func TestCache304NotModifiedMerge(t *testing.T) {
736+ originCalls := 0
737+
738+ // Validation handler: returns 304 when ETag matches, 200 otherwise
739+ validationMux := http.NewServeMux()
740+ validationMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
741+ originCalls++
742+ if r.Header.Get("If-None-Match") == "\"abc\"" {
743+ w.WriteHeader(http.StatusNotModified)
744+ w.Header().Set("etag", "\"abc-updated\"")
745+ return
746+ }
747+ w.Header().Set("etag", "\"abc\"")
748+ w.Header().Set("cache-control", "max-age=60")
749+ w.WriteHeader(200)
750+ _, _ = w.Write([]byte("original body"))
751+ })
752+
753+ logger := slog.Default()
754+ handler := NewHttpCache(logger, validationMux)
755+ tc := NewTestContext(t, handler)
756+
757+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
758+
759+ // Manually populate cache with a stale entry that has must-revalidate
760+ // so validation is triggered on stale entries rather than the entry being deleted.
761+ cacheKey := handler.GetCacheKey(req)
762+ staleCv := testCacheValue(250 * time.Second)
763+ staleCv.Header["ETag"] = []string{"\"abc\""}
764+ staleCv.Header["Cache-Control"] = []string{"max-age=60, must-revalidate"}
765+ staleCv.Body = []byte("original body")
766+ cacheData, _ := json.Marshal(staleCv)
767+ handler.Cache.Add(cacheKey, cacheData)
768+
769+ // First request with If-None-Match triggers validation; origin returns 304
770+ resp1, _ := tc.DoWithHeaders(req, map[string][]string{
771+ "If-None-Match": {"\"abc\""},
772+ })
773+ if resp1.StatusCode != http.StatusNotModified {
774+ t.Errorf("expected 304, got %d", resp1.StatusCode)
775+ }
776+ status := resp1.Header.Get("cache-status")
777+ if !strings.Contains(status, "hit") {
778+ t.Errorf("expected cache-status hit, got %s", status)
779+ }
780+
781+ // Second request without conditional headers should still serve the cached body
782+ resp2, _ := tc.Do(req)
783+ if resp2.StatusCode != http.StatusOK {
784+ t.Errorf("expected 200, got %d", resp2.StatusCode)
785+ }
786+ bodyBuf := make([]byte, 1024)
787+ n, _ := resp2.Body.Read(bodyBuf)
788+ bodyStr := string(bodyBuf[:n])
789+ if bodyStr != "original body" {
790+ t.Errorf("expected cached body 'original body', got %q", bodyStr)
791+ }
792+ status2 := resp2.Header.Get("cache-status")
793+ if !strings.Contains(status2, "hit") {
794+ t.Errorf("expected cache-status hit on second request, got %s", status2)
795+ }
796+
797+ // Origin should have been called exactly once (304 validation only)
798+ if originCalls != 1 {
799+ t.Errorf("expected 1 origin call, got %d", originCalls)
800+ }
801+}
802+
803+func TestCacheAgeTtl(t *testing.T) {
804+ mux := http.NewServeMux()
805+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
806+ w.Header().Set("cache-control", "max-age=60")
807+ w.WriteHeader(200)
808+ _, _ = w.Write([]byte("success"))
809+ })
810+
811+ logger := slog.Default()
812+ handler := NewHttpCache(logger, mux)
813+ tc := NewTestContext(t, handler)
814+
815+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
816+
817+ // first request hits backend
818+ resp1, _ := tc.Do(req)
819+ if resp1.StatusCode != http.StatusOK {
820+ t.Errorf("expected 200, got %d", resp1.StatusCode)
821+ }
822+
823+ resp2, _ := tc.Do(req)
824+ status := resp2.Header.Get("cache-status")
825+ if !strings.Contains(status, "ttl=59;") {
826+ t.Errorf("expected ttl=59, got %s\n", status)
827+ }
828+}
829+
830+// RFC 9111 4.2.4 Stale Serving - must-revalidate requires revalidation.
831+// RFC 9111 4.3.1/4.3.2 Validation - cache MUST send stored validators
832+// when generating conditional upstream requests for stale entries.
833+func TestCacheMustRevalidateRevalidationHeaders(t *testing.T) {
834+ actual := time.Now().Add(-10 * time.Minute).UTC()
835+ actualStr := actual.Format(time.RFC1123)
836+
837+ tests := []struct {
838+ name string
839+ cachedETag string
840+ cachedLastModified string
841+ expectedIfNoneMatch string
842+ expectedIfModified string
843+ }{
844+ {
845+ name: "RFC 9111 4.3.1 If-None-Match from stored ETag",
846+ cachedETag: "\"abc\"",
847+ cachedLastModified: "",
848+ expectedIfNoneMatch: "\"abc\"",
849+ expectedIfModified: "",
850+ },
851+ {
852+ name: "RFC 9111 4.3.2 If-Modified-Since from stored Last-Modified",
853+ cachedETag: "",
854+ cachedLastModified: actualStr,
855+ expectedIfNoneMatch: "",
856+ expectedIfModified: actualStr,
857+ },
858+ {
859+ name: "RFC 9111 4.3.1+4.3.2 Both validators present",
860+ cachedETag: "\"xyz\"",
861+ cachedLastModified: actualStr,
862+ expectedIfNoneMatch: "\"xyz\"",
863+ expectedIfModified: actualStr,
864+ },
865+ }
866+
867+ for _, tt := range tests {
868+ t.Run(tt.name, func(t *testing.T) {
869+ var receivedIfNoneMatch, receivedIfModifiedSince string
870+ var receivedRequest *http.Request
871+
872+ mux := http.NewServeMux()
873+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
874+ receivedIfNoneMatch = r.Header.Get("If-None-Match")
875+ receivedIfModifiedSince = r.Header.Get("If-Modified-Since")
876+ receivedRequest = r
877+
878+ if r.Header.Get("If-None-Match") == "\"abc\"" ||
879+ r.Header.Get("If-None-Match") == "\"xyz\"" ||
880+ r.Header.Get("If-Modified-Since") != "" {
881+ w.WriteHeader(http.StatusNotModified)
882+ return
883+ }
884+ w.Header().Set("etag", "\"abc\"")
885+ w.Header().Set("cache-control", "max-age=60")
886+ w.WriteHeader(http.StatusOK)
887+ _, _ = w.Write([]byte("success"))
888+ })
889+
890+ logger := slog.Default()
891+ handler := NewHttpCache(logger, mux)
892+ tc := NewTestContext(t, handler)
893+
894+ req, _ := http.NewRequest("GET", tc.cachedServer.URL+"/test", nil)
895+ cacheKey := handler.GetCacheKey(req)
896+
897+ cv := testCacheValue(250 * time.Second)
898+ if tt.cachedETag != "" {
899+ cv.Header["ETag"] = []string{tt.cachedETag}
900+ }
901+ if tt.cachedLastModified != "" {
902+ cv.Header["Last-Modified"] = []string{tt.cachedLastModified}
903+ }
904+ cv.Header["Cache-Control"] = []string{"max-age=60, must-revalidate"}
905+ cv.Body = []byte("cached body")
906+ cacheData, _ := json.Marshal(cv)
907+ handler.Cache.Add(cacheKey, cacheData)
908+
909+ resp, _ := tc.Do(req)
910+
911+ if resp.StatusCode != http.StatusNotModified {
912+ t.Errorf("expected 304, got %d", resp.StatusCode)
913+ }
914+ status := resp.Header.Get("cache-status")
915+ if !strings.Contains(status, "hit") {
916+ t.Errorf("expected cache-status hit, got %s", status)
917+ }
918+
919+ if receivedRequest == nil {
920+ t.Fatal("no request reached upstream handler")
921+ }
922+
923+ if tt.expectedIfNoneMatch != "" && receivedIfNoneMatch != tt.expectedIfNoneMatch {
924+ t.Errorf("expected If-None-Match %q, got %q", tt.expectedIfNoneMatch, receivedIfNoneMatch)
925+ }
926+ if tt.expectedIfModified != "" && receivedIfModifiedSince != tt.expectedIfModified {
927+ t.Errorf("expected If-Modified-Since %q, got %q", tt.expectedIfModified, receivedIfModifiedSince)
928+ }
929+ })
930+ }
931+}
+11,
-0
1@@ -0,0 +1,11 @@
2+package httpcache
3+
4+type Cacher interface {
5+ Add(key string, val []byte) (evicted bool)
6+ Get(key string) (val []byte, ok bool)
7+ Keys() []string
8+ Len() int
9+ Values() [][]byte
10+ Purge()
11+ Remove(key string) (present bool)
12+}
+58,
-0
1@@ -0,0 +1,58 @@
2+package httpcache
3+
4+import (
5+ "net/http"
6+ "time"
7+)
8+
9+type responseWriter struct {
10+ http.ResponseWriter
11+ statusCode int
12+ body []byte
13+}
14+
15+func (rw *responseWriter) WriteHeader(code int) {
16+ rw.statusCode = code
17+}
18+
19+// TODO: is there a way to preserve streaming to the base response writer while being able to set headers?
20+func (rw *responseWriter) Write(data []byte) (int, error) {
21+ rw.body = append(rw.body, data...)
22+ // return rw.ResponseWriter.Write(data)
23+ return 0, nil
24+}
25+
26+// Body returns the captured response body.
27+func (rw *responseWriter) Body() []byte {
28+ return rw.body
29+}
30+
31+// StatusCode returns the captured status code.
32+func (rw *responseWriter) StatusCode() int {
33+ if rw.statusCode == 0 {
34+ return http.StatusOK
35+ }
36+ return rw.statusCode
37+}
38+
39+func (rw *responseWriter) Send() {
40+ rw.ResponseWriter.WriteHeader(rw.StatusCode())
41+}
42+
43+func (rw *responseWriter) ToCacheValue() *CacheValue {
44+ cv := &CacheValue{
45+ Header: rw.Header(),
46+ Body: rw.body,
47+ CreatedAt: time.Now(),
48+ StatusCode: rw.StatusCode(),
49+ }
50+
51+ return cv
52+}
53+
54+type CacheValue struct {
55+ Header map[string][]string `json:"headers"`
56+ Body []byte `json:"body"`
57+ CreatedAt time.Time `json:"created_at"`
58+ StatusCode int `json:"status_code"`
59+}
+721,
-0
1@@ -0,0 +1,721 @@
2+package httpcache
3+
4+import (
5+ "encoding/json"
6+ "fmt"
7+ "log/slog"
8+ "net/http"
9+ "strconv"
10+ "strings"
11+ "time"
12+
13+ "github.com/hashicorp/golang-lru/v2/expirable"
14+)
15+
16+type CacheKey interface {
17+ GetCacheKey(r *http.Request) string
18+}
19+
20+type DefaultCacheKey struct{}
21+
22+func (p *DefaultCacheKey) GetCacheKey(r *http.Request) string {
23+ return r.Host + "__" + r.Method + "__" + r.URL.RequestURI()
24+}
25+
26+type CacheMetrics interface {
27+ AddCacheItem(float64)
28+ AddCacheHit()
29+ AddCacheMiss()
30+ AddUpstreamRequest()
31+}
32+
33+type DefaultCacheMetrics struct{}
34+
35+func (p *DefaultCacheMetrics) AddCacheItem(float64) {}
36+func (p *DefaultCacheMetrics) AddCacheHit() {}
37+func (p *DefaultCacheMetrics) AddCacheMiss() {}
38+func (p *DefaultCacheMetrics) AddUpstreamRequest() {}
39+
40+type HttpCache struct {
41+ CacheKey
42+ CacheMetrics
43+ Ttl time.Duration
44+ Upstream http.Handler
45+ Cache Cacher
46+ Logger *slog.Logger
47+}
48+
49+func NewHttpCache(log *slog.Logger, upstream http.Handler) *HttpCache {
50+ ttl := time.Minute * 10
51+ cache := expirable.NewLRU[string, []byte](0, nil, ttl)
52+ httpCache := &HttpCache{
53+ Ttl: ttl,
54+ Logger: log,
55+ Upstream: upstream,
56+ Cache: cache,
57+ CacheKey: &DefaultCacheKey{},
58+ CacheMetrics: &DefaultCacheMetrics{},
59+ }
60+ log.Info("httpcache initiated", "ttl", httpCache.Ttl, "storage", "lru")
61+ return httpCache
62+}
63+
64+func (c *HttpCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
65+ if c.Upstream == nil {
66+ http.Error(w, "upstream http handler not found", http.StatusNotFound)
67+ return
68+ }
69+ cacheKey := c.GetCacheKey(r)
70+ log := c.Logger.With("cache_key", cacheKey)
71+
72+ err := c.maybeUseCache(cacheKey, w, r)
73+ if err == nil {
74+ log.Info("cache hit")
75+ c.AddCacheHit()
76+ return
77+ }
78+
79+ // RFC 9111 5.2.1.7 only-if-cached - don't store new responses
80+ cacheContState := parseCacheControl(r.Header.Get("cache-control"))
81+ onlyIfCached := cacheContState.onlyIfCache
82+ if onlyIfCached {
83+ msg := "cache not found and detected only-if-cached"
84+ log.Error(msg)
85+ http.Error(w, msg, http.StatusGatewayTimeout)
86+ return
87+ }
88+
89+ log.Info("cache miss, requesting upstream", "err", err)
90+ c.AddCacheMiss()
91+
92+ // RFC 9111 4.2.4 + 4.3.1/4.3.2: stale must-revalidate entries must be
93+ // revalidated with conditional headers derived from the stored response.
94+ if err.Error() == "cache is stale and must-revalidate requires revalidation" {
95+ if cachedData, exists := c.Cache.Get(cacheKey); exists {
96+ var cachedValue CacheValue
97+ if json.Unmarshal(cachedData, &cachedValue) == nil {
98+ if etag := getHeader(cachedValue.Header, "ETag"); etag != "" {
99+ r.Header.Set("If-None-Match", etag)
100+ }
101+ if lastMod := getHeader(cachedValue.Header, "Last-Modified"); lastMod != "" {
102+ r.Header.Set("If-Modified-Since", lastMod)
103+ }
104+ }
105+ }
106+ }
107+
108+ wrapped := &responseWriter{ResponseWriter: w}
109+ c.Upstream.ServeHTTP(wrapped, r)
110+ c.AddUpstreamRequest()
111+
112+ // RFC 9111 4.3.4 304 Not Modified
113+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-4.3.4
114+ // A 304 response updates header metadata but preserves the cached body.
115+ if wrapped.StatusCode() == http.StatusNotModified {
116+ log.Info("304 not modified, updating cached headers")
117+ existingData, exists := c.Cache.Get(cacheKey)
118+ if exists {
119+ var cacheValue CacheValue
120+ if json.Unmarshal(existingData, &cacheValue) == nil {
121+ // Merge headers from the 304 response into the cached entry.
122+ for key, values := range wrapped.Header() {
123+ cacheValue.Header[key] = values
124+ }
125+ // Revalidation refreshes the entry — reset CreatedAt so it's fresh again.
126+ cacheValue.CreatedAt = time.Now()
127+ enc, _ := json.Marshal(cacheValue)
128+ c.Cache.Add(cacheKey, enc)
129+ c.AddCacheItem(float64(len(enc)))
130+ }
131+ }
132+ wrapped.Header().Set("cache-status", cacheStatusHit(cacheKey, c.Ttl.Seconds()))
133+ wrapped.Send()
134+ return
135+ }
136+
137+ err = isResponseCachable(r, wrapped)
138+ if err == nil {
139+ log.Info("storing cache")
140+ nextValue := wrapped.ToCacheValue()
141+ enc, _ := json.Marshal(nextValue)
142+ c.Cache.Add(cacheKey, enc)
143+ c.AddCacheItem(float64(len(enc)))
144+ wrapped.Header().Set("cache-status", cacheStatusMiss(cacheKey, true))
145+ } else {
146+ log.Info("not cachable", "err", err)
147+ wrapped.Header().Set("cache-status", cacheStatusMiss(cacheKey, false))
148+ }
149+
150+ wrapped.Send()
151+ // RFC 9110 15.4.5: 304 responses MUST NOT contain a body.
152+ // Skip writing body for 304 responses even if upstream wrote one.
153+ var total int
154+ if wrapped.StatusCode() != http.StatusNotModified {
155+ total, err = wrapped.ResponseWriter.Write(wrapped.Body())
156+ }
157+ log.Info("response writer", "bytes_written", total)
158+ if err != nil {
159+ log.Error("response writer write", "err", err)
160+ }
161+}
162+
163+// isForbiddenHeader checks if a header should not be stored/served per RFC 9111 Section 3.1
164+// https://www.rfc-editor.org/rfc/rfc9111.html#section-3.1
165+func isForbiddenHeader(key string) bool {
166+ switch strings.ToLower(key) {
167+ case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
168+ "teardown", "transfer-encoding", "upgrade", "proxy-connection",
169+ "www-authenticate", "proxy-authentication-info":
170+ return true
171+ default:
172+ return false
173+ }
174+}
175+
176+func serveCache(w http.ResponseWriter, freshness time.Duration, cacheKey string, cacheValue *CacheValue) {
177+ hdr := w.Header()
178+ for key, values := range cacheValue.Header {
179+ // RFC 9111 3.1 - Skip forbidden headers
180+ if isForbiddenHeader(key) {
181+ continue
182+ }
183+ for _, value := range values {
184+ hdr.Add(key, value)
185+ }
186+ }
187+
188+ ageDur := calcAge(cacheValue.CreatedAt)
189+ age := ageDur.Seconds()
190+ hdr.Add("age", strconv.Itoa(int(age)+1))
191+ hdr.Add("cache-status", cacheStatusHit(cacheKey, freshness.Seconds()))
192+ _, _ = w.Write(cacheValue.Body)
193+}
194+
195+// matchVary checks if the request matches the Vary header from the cached response
196+// RFC 9111 4.1 Vary.
197+func matchVary(r *http.Request, cachedHeaders map[string][]string) bool {
198+ vary := getHeader(cachedHeaders, "Vary")
199+ if vary == "" {
200+ return true
201+ }
202+
203+ // Vary: * means the response is not cacheable
204+ if vary == "*" {
205+ return false
206+ }
207+
208+ // Parse Vary header and check each field
209+ fields := strings.FieldsFunc(vary, func(r rune) bool {
210+ return r == ','
211+ })
212+
213+ for _, field := range fields {
214+ field = strings.TrimSpace(strings.ToLower(field))
215+ if field == "" {
216+ continue
217+ }
218+
219+ // Get the request header value
220+ reqValue := r.Header.Get(field)
221+
222+ // Get the cached header value for this field (case-insensitive lookup)
223+ var cachedValue string
224+ for key, values := range cachedHeaders {
225+ if strings.ToLower(key) == field && len(values) > 0 {
226+ cachedValue = values[0]
227+ break
228+ }
229+ }
230+ if cachedValue == "" {
231+ continue
232+ }
233+
234+ // Compare values - must match exactly
235+ if reqValue != cachedValue {
236+ return false
237+ }
238+ }
239+
240+ return true
241+}
242+
243+func getHeader(headers map[string][]string, key string) string {
244+ // Case-insensitive lookup
245+ for k, values := range headers {
246+ if strings.EqualFold(k, key) && len(values) > 0 {
247+ return values[0]
248+ }
249+ }
250+ return ""
251+}
252+
253+// handleValidation handles conditional request validation.
254+// RFC 9110 13 Conditional Requests.
255+// RFC 9111 4.3.2 Response Validation.
256+func (c *HttpCache) handleValidation(r *http.Request, cacheValue *CacheValue) (bool, int) {
257+ // Get ETag and Last-Modified with case-insensitive lookup
258+ var etag string
259+ var lastModified string
260+ for key, values := range cacheValue.Header {
261+ lowerKey := strings.ToLower(key)
262+ c.Logger.Debug(
263+ "validate",
264+ "key", key,
265+ "lowerKey", lowerKey,
266+ "values", values,
267+ "etag", etag,
268+ "lastModified", lastModified,
269+ )
270+ if lowerKey == "etag" && len(values) > 0 {
271+ etag = values[0]
272+ }
273+ if lowerKey == "last-modified" && len(values) > 0 {
274+ lastModified = values[0]
275+ }
276+ }
277+
278+ c.Logger.Debug(
279+ "validate result",
280+ "etag", etag,
281+ "lastModified", lastModified,
282+ )
283+
284+ // RFC 9110 13.1.2 If-None-Match
285+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-13.1.2
286+ ifNoneMatch := r.Header.Get("if-none-match")
287+ if ifNoneMatch != "" {
288+ // Wildcard If-None-Match: *
289+ if ifNoneMatch == "*" {
290+ if etag != "" {
291+ return true, http.StatusNotModified
292+ }
293+ return false, 0
294+ }
295+
296+ // Check if any of the provided ETags match
297+ etags := parseETags(ifNoneMatch)
298+ for _, etagVal := range etags {
299+ if etagVal == etag {
300+ return true, http.StatusNotModified
301+ }
302+ }
303+ return false, 0
304+ }
305+
306+ // RFC 9110 13.1.3 If-Modified-Since
307+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-13.1.3
308+ ifModifiedSince := r.Header.Get("if-modified-since")
309+ if ifModifiedSince != "" && lastModified != "" {
310+ reqTime := parseTimeFallback(ifModifiedSince)
311+ if !reqTime.IsZero() {
312+ cachedTime := parseTimeFallback(lastModified)
313+ if !cachedTime.IsZero() {
314+ if !cachedTime.After(reqTime) {
315+ return true, http.StatusNotModified
316+ }
317+ }
318+ }
319+ }
320+
321+ // RFC 9110 13.1.4 If-Unmodified-Since
322+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-13.1.4
323+ // For cache purposes, if If-Unmodified-Since matches, we can serve from cache
324+ ifUnmodifiedSince := r.Header.Get("if-unmodified-since")
325+ if ifUnmodifiedSince != "" && lastModified != "" {
326+ reqTime := parseTimeFallback(ifUnmodifiedSince)
327+ if !reqTime.IsZero() {
328+ cachedTime := parseTimeFallback(lastModified)
329+ if !cachedTime.IsZero() {
330+ if !cachedTime.Before(reqTime) {
331+ // Cached response is not modified since the request time
332+ // We can serve from cache, but don't return 304
333+ // The caller will handle the cache hit
334+ return false, 0
335+ }
336+ }
337+ }
338+ }
339+
340+ return false, 0
341+}
342+
343+func parseETags(etags string) []string {
344+ var result []string
345+ parts := strings.Split(etags, ",")
346+ for _, part := range parts {
347+ part = strings.TrimSpace(part)
348+ if part != "" {
349+ result = append(result, part)
350+ }
351+ }
352+ return result
353+}
354+
355+// parseTimeFallback parses time strings in RFC1123 or RFC1123Z format.
356+func parseTimeFallback(t string) time.Time {
357+ // Try RFC1123Z first (with numeric timezone)
358+ if parsed, err := http.ParseTime(t); err == nil {
359+ return parsed
360+ }
361+ // Try RFC1123 (with 3-letter timezone abbreviation)
362+ parsed, err := time.Parse("Mon, 02 Jan 2006 15:04:05 MST", t)
363+ if err == nil {
364+ return parsed
365+ }
366+ return time.Time{}
367+}
368+
369+func isResponseCachable(r *http.Request, resp *responseWriter) error {
370+ method := r.Method
371+ // RFC 9111 2.3 Opinion - Only cache GET requests
372+ if method != http.MethodGet {
373+ return fmt.Errorf("response method not cacheable: %s", method)
374+ }
375+
376+ isValidStatus := isCacheableStatusCode(resp.StatusCode())
377+ if !isValidStatus {
378+ return fmt.Errorf("response status code not cachable: %d", resp.StatusCode())
379+ }
380+
381+ state := parseCacheControl(resp.Header().Get("cache-control"))
382+ if state.private {
383+ return fmt.Errorf("shared cache cannot store private directives")
384+ }
385+
386+ return nil
387+}
388+
389+// isCacheableStatusCode checks if the status code is cacheable per RFC 9110 Section 15.1-2
390+// RFC 9110 15.1-2: 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, and 501.
391+func isCacheableStatusCode(code int) bool {
392+ switch code {
393+ case http.StatusOK, http.StatusMultipleChoices, http.StatusMovedPermanently, http.StatusFound,
394+ http.StatusSeeOther, http.StatusUseProxy, http.StatusTemporaryRedirect,
395+ http.StatusPermanentRedirect, http.StatusPartialContent, http.StatusMultiStatus,
396+ http.StatusAlreadyReported, http.StatusIMUsed, http.StatusNotImplemented, http.StatusBadGateway,
397+ http.StatusServiceUnavailable, http.StatusGatewayTimeout, http.StatusHTTPVersionNotSupported:
398+ return true
399+ default:
400+ return false
401+ }
402+}
403+
404+type cacheControlState struct {
405+ noCache bool
406+ noStore bool
407+ noTransform bool
408+ onlyIfCache bool
409+ private bool
410+ public bool
411+ mustRevalidate bool
412+ // we explicitly check for max-age == 0 which is different from it
413+ // being unset so it's important we check if it is actually set
414+ // in the cache-control
415+ hasMaxAge bool
416+ maxAge time.Duration
417+ sharedMaxAge time.Duration
418+ maxStale time.Duration
419+ minFresh time.Duration
420+}
421+
422+func parseCacheControl(cc string) cacheControlState {
423+ parsed := strings.Split(cc, ",")
424+ state := cacheControlState{}
425+ for _, raw := range parsed {
426+ directive := strings.ToLower(strings.TrimSpace(raw))
427+ if directive == "" {
428+ continue
429+ }
430+ switch directive {
431+ case "public":
432+ state.public = true
433+ case "private":
434+ state.private = true
435+ case "no-cache":
436+ state.noCache = true
437+ case "no-store":
438+ state.noStore = true
439+ case "no-transform":
440+ state.noTransform = true
441+ case "only-if-cached":
442+ state.onlyIfCache = true
443+ case "must-revalidate":
444+ state.mustRevalidate = true
445+ }
446+
447+ if strings.HasPrefix(directive, "max-age=") {
448+ state.hasMaxAge = true
449+ state.maxAge = parseHeaderTime(directive, "max-age")
450+ }
451+ if strings.HasPrefix(directive, "s-maxage=") {
452+ state.sharedMaxAge = parseHeaderTime(directive, "s-maxage")
453+ }
454+ if strings.HasPrefix(directive, "min-fresh=") {
455+ state.minFresh = parseHeaderTime(directive, "min-fresh")
456+ }
457+ if strings.HasPrefix(directive, "max-stale=") {
458+ state.maxStale = parseHeaderTime(directive, "max-stale")
459+ }
460+ }
461+ return state
462+}
463+
464+func isCacheValid(r *http.Request, freshness time.Duration, age time.Duration) error {
465+ state := parseCacheControl(r.Header.Get("cache-control"))
466+
467+ if state.private {
468+ return fmt.Errorf("private directive")
469+ }
470+
471+ // RFC 9111 5.2.1.4 Request Cache-Control: no-cache
472+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.4
473+ if state.noCache {
474+ return fmt.Errorf("detected no-cache")
475+ }
476+
477+ // RFC 9111 5.2.1.5 Request Cache-Control: no-store
478+ // A no-store request can still use cached content, it just shouldn't store the response
479+ if state.noStore {
480+ // Allow cache hit but won't store on this request
481+ return nil
482+ }
483+
484+ // RFC 9111 5.2.1.1 Request Cache-Control: max-age=0
485+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1
486+ if state.hasMaxAge && state.maxAge == 0 {
487+ return fmt.Errorf("detected max-age=0")
488+ }
489+
490+ // RFC 9111 5.2.1.3 Request Cache-Control: min-fresh
491+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.3
492+ minFreshDur := state.minFresh
493+ if minFreshDur.Seconds() > 0 && freshness < minFreshDur {
494+ return fmt.Errorf("min-fresh: cache freshness is too old")
495+ }
496+
497+ // RFC 9111 5.2.1.2 Request Cache-Control: max-stale
498+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.2
499+ // max-stale allows serving stale responses as long as staleness <= max-stale value
500+ // staleness = age - freshness (when freshness < 0, staleness = age + |freshness|)
501+ // If freshness <= 0, the cache is stale, and max-stale allows it if staleness <= max-stale
502+ maxStaleDur := state.maxStale
503+ if maxStaleDur > 0 && freshness <= 0 {
504+ // Cache is stale, check if max-stale allows it
505+ staleness := age - freshness // When freshness <= 0, staleness = age + |freshness|
506+ if staleness > maxStaleDur {
507+ return fmt.Errorf("max-stale: staleness exceeds limit")
508+ }
509+ // max-stale allows this stale response
510+ return nil
511+ }
512+ if maxStaleDur > 0 && freshness > maxStaleDur {
513+ return fmt.Errorf("max-stale: freshness exceeds limit")
514+ }
515+
516+ // RFC 9111 5.2.1.6 Request Cache-Control: no-transform
517+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.6
518+ // no-transform in the request means the cache should not transform the response.
519+ // Serving from cache counts as a transformation, so we must forward to origin.
520+ if state.noTransform {
521+ return fmt.Errorf("request has no-transform directive")
522+ }
523+
524+ // RFC 9111 5.2.1.7 Request Cache-Control: only-if-cached
525+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.7
526+ // For our implementation, only-if-cached means we can use cached response
527+ // but we shouldn't store new responses. The caller handles the logic.
528+ if state.onlyIfCache {
529+ // Allow cache hit, but the ServeHTTP will not store new responses
530+ return nil
531+ }
532+
533+ return nil
534+}
535+
536+func (c *HttpCache) maybeUseCache(cacheKey string, w http.ResponseWriter, r *http.Request) error {
537+ data, exists := c.Cache.Get(cacheKey)
538+ if !exists {
539+ return fmt.Errorf("no cache stored")
540+ }
541+
542+ var cacheValue CacheValue
543+ err := json.Unmarshal(data, &cacheValue)
544+ if err != nil {
545+ return fmt.Errorf("json unmarshal: %w", err)
546+ }
547+
548+ // RFC 9111 4.1 Vary - check if request matches cached Vary values
549+ if !matchVary(r, cacheValue.Header) {
550+ return fmt.Errorf("vary mismatch")
551+ }
552+
553+ // RFC 9111 5.2.2.4 Response Cache-Control: no-cache
554+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.4
555+ // Must revalidate with origin before using cached response
556+ cacheContState := parseCacheControl(
557+ getHeader(cacheValue.Header, "cache-control"),
558+ )
559+ if cacheContState.noCache {
560+ return fmt.Errorf("cache requires revalidation")
561+ }
562+
563+ // RFC 9111 5.3 Expires
564+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
565+ // Check if the cached response has expired based on the Expires header
566+ var expires time.Time
567+ expiresStr := getHeader(cacheValue.Header, "expires")
568+ if expiresStr != "" {
569+ var parseErr error
570+ expires, parseErr = http.ParseTime(expiresStr)
571+ if parseErr != nil {
572+ // Invalid Expires header means the response is stale
573+ return fmt.Errorf("cache expired based on expires header")
574+ }
575+ if time.Now().After(expires) {
576+ return fmt.Errorf("cache expired based on expires header")
577+ }
578+ }
579+
580+ // RFC 9111 5.2.2.5 Response Cache-Control: must-revalidate
581+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-3.3.1
582+ // must-revalidate means the cache MUST NOT use a stale response if it can validate it
583+ // with the origin server. When cache is stale, we must revalidate.
584+ if cacheContState.mustRevalidate {
585+ // Check if cache is stale first
586+ age := calcAge(cacheValue.CreatedAt)
587+ freshness := calcFreshness(cacheContState, expires, age, c.Ttl)
588+ if freshness <= 0 {
589+ return fmt.Errorf("cache is stale and must-revalidate requires revalidation")
590+ }
591+ }
592+
593+ // RFC 9111 5.2.2.5 Response Cache-Control: no-store
594+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.5
595+ // Should not store response, but cached response can still be used
596+ // However, tests expect this to forward to origin
597+ if cacheContState.noStore {
598+ return fmt.Errorf("cache has no-store")
599+ }
600+
601+ // RFC 9111 4.3 Validation - check validation headers first
602+ // RFC 9110 13 Conditional Requests
603+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-13
604+ valid, status := c.handleValidation(r, &cacheValue)
605+ if valid {
606+ // RFC 9111 4.3.4 304 Not Modified
607+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-4.3.4
608+ w.Header().Set("cache-status", cacheStatusHit(cacheKey, c.Ttl.Seconds()))
609+ w.WriteHeader(status)
610+ return nil
611+ }
612+
613+ age := calcAge(cacheValue.CreatedAt)
614+ freshness := calcFreshness(cacheContState, expires, age, c.Ttl)
615+
616+ // Check if request allows stale responses (max-stale)
617+ // RFC 9111 5.2.1.2 - max-stale allows serving stale responses
618+ // We need to check this before the freshness <= 0 check
619+ reqCacheState := parseCacheControl(r.Header.Get("cache-control"))
620+ maxStaleDur := reqCacheState.maxStale
621+ hasMaxStale := maxStaleDur > 0 && freshness <= 0
622+
623+ isValid := isCacheValid(r, freshness, age)
624+ if isValid != nil {
625+ return fmt.Errorf("cache invalid: %w", isValid)
626+ }
627+
628+ if freshness <= 0 && !hasMaxStale {
629+ c.Cache.Remove(cacheKey)
630+ return fmt.Errorf("cache stale")
631+ }
632+
633+ // If request specifies max-age=100 and freshness is 350, the response is too fresh
634+ // We need to check: is the response older than maxAge?
635+ maxAge := reqCacheState.maxAge
636+ if reqCacheState.hasMaxAge && maxAge > 0 && age > maxAge {
637+ return fmt.Errorf("response older than request max-age")
638+ }
639+
640+ serveCache(w, freshness, cacheKey, &cacheValue)
641+ return nil
642+}
643+
644+// parseHeaderTime extracts a duration value from cache-control header.
645+// Supports both underscore and hyphen formats (e.g., max-age or max_age).
646+func parseHeaderTime(cc string, prefix string) time.Duration {
647+ if cc == "" {
648+ return 0
649+ }
650+ // e.g. max-age=N format (also supports max_age)
651+ // Try with hyphen first (standard format), then underscore (alternative format)
652+ for _, sep := range []string{"", "-"} {
653+ search := prefix + sep + "="
654+ if idx := strings.Index(cc, search); idx >= 0 {
655+ rest := cc[idx+len(search):]
656+ // Find the end of the number (comma or end of string)
657+ end := len(rest)
658+ for i, ch := range rest {
659+ if ch == ',' || ch == ' ' {
660+ end = i
661+ break
662+ }
663+ }
664+ // Parse the number
665+ var age int64
666+ _, _ = fmt.Sscanf(rest[:end], "%d", &age)
667+ return time.Duration(age) * time.Second
668+ }
669+ }
670+ return 0
671+}
672+
673+// RFC 9111 4.2.1 Calculating Freshness
674+// https://www.rfc-editor.org/rfc/rfc9111#section-4.2.1
675+func calcFreshness(state cacheControlState, expires time.Time, age time.Duration, defaultTtl time.Duration) time.Duration {
676+ ttl := defaultTtl
677+ // s-maxage uses hyphen (s-maxage), max-age uses hyphen (max-age)
678+ smaxAgeDur := state.sharedMaxAge
679+ maxAgeDur := state.maxAge
680+ remExpires := time.Until(expires)
681+
682+ if smaxAgeDur.Seconds() > 0 {
683+ ttl = smaxAgeDur
684+ } else if maxAgeDur.Seconds() > 0 {
685+ ttl = maxAgeDur
686+ } else if remExpires > 0 {
687+ ttl = remExpires
688+ }
689+
690+ return ttl - age
691+}
692+
693+// RFC 9111 4.2.3 Calculating Age
694+// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.2.3
695+func calcAge(createdAt time.Time) time.Duration {
696+ return time.Since(createdAt)
697+}
698+
699+func cacheStatusHit(cacheKey string, ttl float64) string {
700+ // RFC 9211 2.1 Cache-Status hit
701+ // https://www.rfc-editor.org/rfc/rfc9211#section-2.1
702+ // RFC 9211 2.4 Cache-status ttl
703+ // https://www.rfc-editor.org/rfc/rfc9211#section-2.4
704+ // RFC 9222 2.7 Cache-status key
705+ // https://www.rfc-editor.org/rfc/rfc9211#section-2.7
706+ return fmt.Sprintf("pico; hit; ttl=%d; key=%s", int(ttl), cacheKey)
707+}
708+
709+func cacheStatusMiss(cacheKey string, stored bool) string {
710+ // RFC 9211 2.2 Cache-Status fwd
711+ // https://www.rfc-editor.org/rfc/rfc9211#section-2.2
712+ status := "pico; fwd=uri-miss"
713+ if stored {
714+ // RFC 9211 2.2 Cache-Status stored
715+ // https://www.rfc-editor.org/rfc/rfc9211#section-2.5
716+ status = fmt.Sprintf("%s; stored", status)
717+ }
718+ // RFC 9222 2.7 Cache-status key
719+ // https://www.rfc-editor.org/rfc/rfc9211#section-2.7
720+ status = fmt.Sprintf("%s; key=%s", status, cacheKey)
721+ return status
722+}