diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..49b20d7d34584f54b8a7b57d10c8ab5ca7cbb608 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Built binaries +/doltsrht +/doltsrht-migrate + +# Built CSS (generated by `make css`) +/static/main.css +/static/main.min.css +/static/main.min.*.css + +# macOS +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d8ac66d2a79e772f9b89fecf365830d1593cea1f --- /dev/null +++ b/Makefile @@ -0,0 +1,85 @@ +PREFIX?=/usr/local +BINDIR?=$(PREFIX)/bin +LIBDIR?=$(PREFIX)/lib +SHAREDIR?=$(PREFIX)/share + +ASSETS?=/usr/share/sourcehut + +SERVICE=dolt.sr.ht +STATICDIR=$(SHAREDIR)/sourcehut/static/$(SERVICE) +MIGRATIONDIR=$(SHAREDIR)/sourcehut/migrations/$(SERVICE) + +SASSC?=sassc +SASSC_INCLUDE=-I$(ASSETS)/scss/ +MINIFY?=minify + +BINARIES=\ + doltsrht \ + doltsrht-migrate + +# Default target builds the Go binaries only. CSS (all-share) is deliberately +# kept off the default path because sassc/minify are not always installed on +# dev machines; run `make css` explicitly to build stylesheets. +all: all-bin + +all-bin: $(BINARIES) + +all-share: static/main.min.css + +css: all-share + +# Build each binary if its cmd package exists yet. The cmd/ packages land in +# Phase 3; until then these targets are no-ops rather than hard failures. +doltsrht: + @if [ -d ./cmd/doltsrht ]; then \ + echo "go build -o $@ ./cmd/doltsrht"; \ + go build -o $@ ./cmd/doltsrht; \ + else \ + echo "skip $@: ./cmd/doltsrht not present yet"; \ + fi + +doltsrht-migrate: + @if [ -d ./cmd/doltsrht-migrate ]; then \ + echo "go build -o $@ ./cmd/doltsrht-migrate"; \ + go build -o $@ ./cmd/doltsrht-migrate; \ + else \ + echo "skip $@: ./cmd/doltsrht-migrate not present yet"; \ + fi + +# Compile every buildable package; used as the CI build gate. +build: + go build ./... + +install: install-bin install-share + +install-bin: all-bin + mkdir -p $(BINDIR) + for bin in $(BINARIES); do \ + if [ -x $$bin ]; then install -Dm755 $$bin $(BINDIR)/; fi; \ + done + +install-share: + mkdir -p $(STATICDIR) + mkdir -p $(MIGRATIONDIR) + install -Dm644 schema.sql $(SHAREDIR)/sourcehut/$(SERVICE).sql + install -Dm644 migrations/*.sql $(MIGRATIONDIR) + if [ -d static ]; then install -Dm644 static/*.css static/*.svg $(STATICDIR) 2>/dev/null || true; fi + +clean: clean-bin clean-share + +clean-bin: + rm -f $(BINARIES) + +clean-share: + rm -f static/main.min.css static/main.css static/main.min.*.css + +.PHONY: all all-bin all-share css build install install-bin install-share +.PHONY: clean clean-bin clean-share $(BINARIES) + +static/main.css: scss/main.scss + mkdir -p $(@D) + $(SASSC) $(SASSC_INCLUDE) $< $@ + +static/main.min.css: static/main.css + $(MINIFY) -o $@ $< + cp $@ $(@D)/main.min.$$(sha256sum $@ | cut -c1-8).css diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ddc432248f53ade0887465b8701f652bd4d65aee --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# dolt.sr.ht + +A self-hosted [Dolt](https://www.dolthub.com/) database hosting service for a +SourceHut instance — "DoltLab for SourceHut". It hosts Dolt databases the way +git.sr.ht hosts git repos: `dolt clone`/`push`/`pull` over HTTPS plus an +integrated web UI that shares the SourceHut nav, unified login, and Bootstrap +theme. + +Pure Go, one module (`go.bigb.es/sourcehut-dolt`), no upstream SourceHut +modification — integration is config-driven: a `[dolt.sr.ht]` section in the +shared instance `config.ini` puts the service into every other service's nav. + +Storage is bare NBS chunk-store directories (no `.dolt/`, no working set) at +`/~/` — exactly what `remotesrv` serves and `file://` +remotes use. PostgreSQL holds metadata (a mirror of meta's users, plus +repositories, ACLs, and dolt keys). Clone URL: +`dolt clone https://dolt.srht.bigb.es/~user/db`. + +Two auth flows are supported: username + meta personal access token +(`--user` + `DOLT_REMOTE_PASSWORD`, HTTP Basic) and dolt's Ed25519 keypair flow +(`dolt creds` / `dolt login`, Bearer EdDSA JWT — the git-SSH-key-like UX). + +## Status + +Phase 0 (foundation) only: the pure `core/` domain (name/path validation and +the access matrix), the SQL schema and brant migration, config/nginx/Makefile +scaffolding, and the de-risking storage spike. The service binaries, database +layer, auth, remotesapi, and web UI land in later phases. + +## Build prerequisites + +- **Go 1.26+** +- **A C toolchain** — `github.com/dolthub/dolt/go` uses CGO for + [`gozstd`](https://github.com/valyala/gozstd) and + [`go-icu-regex`](https://github.com/dolthub/go-icu-regex). +- **ICU4C development headers** — required by `go-icu-regex`. + - Debian/Ubuntu: `apt install libicu-dev` (headers on the default path). + - macOS (Homebrew): `brew install icu4c` installs a keg-only, versioned + formula. Point CGO at it, e.g. for `icu4c@78`: + + ```sh + export CGO_CPPFLAGS="-I/opt/homebrew/opt/icu4c@78/include" + export CGO_LDFLAGS="-L/opt/homebrew/opt/icu4c@78/lib" + ``` + +- **sassc + minify** — only for building CSS (`make css`); not needed for the + default build: + + ```sh + brew install sassc # or apt install sassc + go install github.com/tdewolff/minify/v2/cmd/minify@latest + ``` + + CSS is compiled against the shared sourcehut SCSS: `make css ASSETS=/path/to/sourcehut/scss/parent` + (`ASSETS` defaults to `/usr/share/sourcehut`; `sassc` is invoked with + `-I $(ASSETS)/scss`). + +## Pinned dependencies (and why) + +- **`git.sr.ht/~sircmpwn/core-go` v0.0.0-20260520082310-fdb3662452dc** — this + exact pseudo-version matches the production instance's `core-go` submodule + commit (`fdb3662`). **Never upgrade it** (no bare `go get -u`, no `@latest`); + token validation, config, and crypto must behave identically to the rest of + the instance. +- **`github.com/dolthub/dolt/go` v0.40.5-0.20260626152440-45335d44ad79** — a + pseudo-version pinned to the commit tagged **v2.1.10** (`45335d44`), the dolt + CLI version installed on the target host (`/opt/homebrew/bin/dolt`, v2.1.10). + The dolt `/go` submodule's latest *tag* is the stale `v0.40.4` (2021), which + does **not** interop with a modern CLI; matching the CLI's commit guarantees a + common NBS storage format (`types.Format_DOLT` / `__DOLT__`) and remotesapi + proto. Verified end-to-end by the Phase-0 spike (see below). If the CLI is + upgraded, re-pin `dolt/go` to the new CLI's commit and re-run the spike. +- **`gopkg.in/go-jose/go-jose.v2` v2.6.3** — the same JOSE major/version that + `dolt/go`'s `creds` package uses to sign the EdDSA keypair JWTs, so the Bearer + verify path stays byte-compatible and no duplicate JOSE lib is pulled in. +- grpc v1.79.3, logrus v1.8.3, lib/pq v1.10.9, chi/v5, and brant round out the + transport, logging, Postgres driver, HTTP router, and migration tooling. + +## The spike + +`storage/spike_test.go` (build tag `spike`) is the Phase-0 de-risk gate. It +inits a bare NBS store via `doltdb.LoadDoltDB` + `WriteEmptyRepo`, serves it +with `remotesrv.NewServer` on an ephemeral localhost port (single-port +http+gRPC multiplex, no auth), then drives the real `dolt` CLI through a full +round-trip: clone → create table + insert → commit → push → fresh re-clone → +verify the rows. It skips (does not fail) when the CLI is absent, and uses an +isolated `$HOME` so your real dolt config is untouched. + +```sh +export CGO_CPPFLAGS="-I/opt/homebrew/opt/icu4c@78/include" +export CGO_LDFLAGS="-L/opt/homebrew/opt/icu4c@78/lib" +go test -tags spike ./storage/ -run TestSpike -v +``` + +## Dev commands + +```sh +go build ./... # build every package (needs the CGO env above) +go test ./core/... # pure-domain unit tests +go test -tags spike ./storage/ -v # the interop spike +make # build the service binaries (no-ops until Phase 3) +make css # build stylesheets (needs sassc + minify) +``` + +## Deployment sketch + +1. Add a `[dolt.sr.ht]` section to the shared instance `config.ini` (see + `config.example.ini`) plus an `origin=` line so the service appears in every + other service's nav. This host **must** be inside meta's `internal-ipnet` + (token validation uses internal auth against meta). +2. Create the database and run migrations: `doltsrht-migrate` applies + `migrations/*.sql` (brant format). `schema.sql` is the full init DDL. +3. Install the nginx site config (`contrib/dolt.sr.ht.conf`) and add a + `dolt.srht.bigb.es` DNS record. nginx path-routes the remotesapi gRPC, the + sealed-URL chunk data plane, the CredentialsService, and the web UI to + separate listeners. +4. Start `doltsrht`. diff --git a/config.example.ini b/config.example.ini new file mode 100644 index 0000000000000000000000000000000000000000..32d95d151ee76855200ccad83f0bdc57d069448f --- /dev/null +++ b/config.example.ini @@ -0,0 +1,53 @@ +; dolt.sr.ht configuration. +; +; In production this section is merged into the single shared instance +; config.ini (the same file every *.sr.ht service reads). Only the keys in the +; [dolt.sr.ht] section below are ours; the rest are shared keys owned by other +; services and referenced (NOT duplicated) here — they must already be present +; and consistent across the instance. +; +; Deployment prerequisite: this host must be inside meta's [meta.sr.ht::api] +; internal-ipnet, because token validation (FetchMetaProfile + +; LookupTokenRevocation) uses internal authentication against meta. + +[dolt.sr.ht] +; +; The URL dolt.sr.ht is served at (protocol://domain). This is also the JWT +; audience the Bearer (dolt creds) auth flow checks against, so it must match +; the host clients pass to `dolt login --auth-endpoint`. +origin=https://dolt.srht.bigb.es +; +; PostgreSQL connection string for the dolt.sr.ht metadata database (users +; mirror, repositories, ACLs, dolt keys). +connection-string=postgresql://doltsrht@localhost/dolt.sr.ht?sslmode=disable +; +; Root directory holding the bare NBS chunk-store dirs, one per database at +; /~/. This is what remotesrv serves and file:// remotes use. +repos=/var/lib/dolt +; +; Address for the remotesapi listener (gRPC ChunkStoreService + HTTP chunk data +; plane, h2c-multiplexed on one port). nginx grpc_pass/proxy_pass targets this. +remotesapi-listen=127.0.0.1:5306 +; +; Address for the small CredentialsService.WhoAmI gRPC listener used by the +; `dolt login` keypair flow. nginx path-routes the CredentialsService here. +credsapi-listen=127.0.0.1:5308 +; +; Directory containing the built static assets (main.min..css, logo.svg). +static-dir=/usr/share/sourcehut/dolt.sr.ht/static +; +; Set to "yes" to run brant migrations automatically on package upgrade. +migrate-on-upgrade=yes + +; --------------------------------------------------------------------------- +; Shared keys reused in place (owned by other services, listed for reference; +; do not duplicate their values here — they live in the shared config.ini): +; +; [sr.ht] network-key shared secret for internal service auth +; [sr.ht] site-name used to render the shared nav brand +; [sr.ht] environment non-"production" adds a banner to every page +; [sr.ht] owner-name / owner-email author of the initial empty commit +; [webhooks] private-key derives the offline Bearer-token HMAC key +; [meta.sr.ht] origin meta's URL (login redirects, profile fetch) +; [meta.sr.ht::api] internal-ipnet subnets allowed to use internal auth +; --------------------------------------------------------------------------- diff --git a/contrib/dolt.sr.ht.conf b/contrib/dolt.sr.ht.conf new file mode 100644 index 0000000000000000000000000000000000000000..e3cc9c2de29a0fcf940b93b6e484bf1f5383bf7a --- /dev/null +++ b/contrib/dolt.sr.ht.conf @@ -0,0 +1,64 @@ +# nginx site config for dolt.sr.ht, in the sr.ht-nginx style. Drop into the +# nginx sites dir alongside the other *.sr.ht.conf files; TLS/http2 and the +# shared proxy headers come from the included snippets (sourcehut.conf, +# port443.conf, port80.conf). +# +# Three back-end listeners are multiplexed by path: +# - the remotesapi gRPC ChunkStoreService and its sealed-URL chunk data plane +# -> 127.0.0.1:5306 +# - the CredentialsService (dolt login / keypair WhoAmI) -> 127.0.0.1:5308 +# - everything else (web UI + /static) -> 127.0.0.1:5307 + +server { + include sourcehut.conf; + include port80.conf; + server_name dolt.srht.bigb.es; +} + +server { + include sourcehut.conf; + include port443.conf; + server_name dolt.srht.bigb.es; + + # dolt push can stream arbitrarily large table files. + client_max_body_size 0; + + # remotesapi control plane: gRPC ChunkStoreService (clone/pull/push RPCs). + # X-Forwarded-Proto must be set so the server hands back https:// sealed + # chunk URLs (getScheme honors this header). + location /dolt.services.remotesapi.v1alpha1.ChunkStoreService/ { + grpc_pass grpc://127.0.0.1:5306; + grpc_set_header X-Forwarded-Proto https; + grpc_read_timeout 600s; + grpc_send_timeout 600s; + client_max_body_size 0; + } + + # CredentialsService.WhoAmI: the `dolt login` keypair association flow. + location /dolt.services.remotesapi.v1alpha1.CredentialsService/ { + grpc_pass grpc://127.0.0.1:5308; + grpc_set_header X-Forwarded-Proto https; + } + + # Chunk data plane: AES-GCM sealed URLs (possession == authorization), + # 15-minute expiry. GET on pull, PUT on push. No extra auth needed. + location /single_symmetric_key_sealed_request/ { + proxy_pass http://127.0.0.1:5306; + client_max_body_size 0; + proxy_request_buffering off; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + } + + # Web UI and /static. + location / { + proxy_pass http://127.0.0.1:5307; + include headers.conf; + include web.conf; + } + + location /static { + root /usr/share/sourcehut; + expires 30d; + } +} diff --git a/core/access.go b/core/access.go new file mode 100644 index 0000000000000000000000000000000000000000..75bd8fe1899f14f9b03cdb61c4d8019ecd185c52 --- /dev/null +++ b/core/access.go @@ -0,0 +1,85 @@ +package core + +// Allowed decides whether caller may perform op on repo, given the caller's +// resolved per-repo ACL grant (aclMode, nil if the caller has no ACL entry). +// It implements the dolt.sr.ht access matrix exactly: +// +// Caller \ Visibility | PUBLIC | UNLISTED | PRIVATE +// --------------------|---------------|---------------|---------------------- +// anon / any user | browse, clone | browse, clone | — +// ACL RO | browse, clone | browse, clone | browse, clone +// ACL RW | + push | + push | browse, clone, push +// owner | all | all | all +// +// Rules: +// - The owner may do anything (subject to the suspension rule below). +// - An ACL RO grant permits browse and clone on any visibility, including +// PRIVATE. +// - An ACL RW grant additionally permits push (but never admin). +// - With no ACL and non-owner: PUBLIC and UNLISTED permit browse and clone +// for everyone (including anonymous); PRIVATE permits nothing. +// - A suspended caller may read (browse, clone) but never push or admin, +// regardless of ownership or ACL. +// - Any operation outside the four known Ops is denied. +// +// Access control fails closed: a nil repo denies everything. +func Allowed(caller *Caller, repo *Repo, aclMode *AccessMode, op Op) bool { + switch op { + case OpBrowse, OpCloneRead, OpPush, OpAdmin: + // known op + default: + return false + } + if repo == nil { + return false + } + + suspended := caller != nil && caller.Suspended + isWrite := op == OpPush || op == OpAdmin + if suspended && isWrite { + return false + } + + // Owner may do anything (write already gated by the suspension check). + if caller != nil && caller.UserID == repo.OwnerID { + return true + } + + // Explicit ACL grants (only meaningful for authenticated callers). + if caller != nil && aclMode != nil { + switch *aclMode { + case AccessRO: + return op == OpBrowse || op == OpCloneRead + case AccessRW: + // RO reads plus push; never admin. + return op == OpBrowse || op == OpCloneRead || op == OpPush + } + } + + // No ownership, no ACL: fall back to visibility. Only reads are ever + // granted this way. + switch repo.Visibility { + case VisibilityPublic, VisibilityUnlisted: + return op == OpBrowse || op == OpCloneRead + default: // VisibilityPrivate and anything unrecognized + return false + } +} + +// NotFoundForPrivate reports whether a denied request for repo should be +// surfaced as "not found" (404) rather than "forbidden" (403), so that the +// existence of PRIVATE repositories is not leaked. Consult it only after +// Allowed has already returned false for the request. +// +// A PRIVATE repo that the caller cannot even browse is reported as not found; +// PUBLIC/UNLISTED repos, and PRIVATE repos the caller may browse, reveal their +// existence normally (a plain 403). A nil repo is always "not found". +func NotFoundForPrivate(caller *Caller, repo *Repo, aclMode *AccessMode) bool { + if repo == nil { + return true + } + if repo.Visibility != VisibilityPrivate { + return false + } + return !Allowed(caller, repo, aclMode, OpBrowse) +} diff --git a/core/access_test.go b/core/access_test.go new file mode 100644 index 0000000000000000000000000000000000000000..263c3df7c5b9a978584d710a384298c676999170 --- /dev/null +++ b/core/access_test.go @@ -0,0 +1,142 @@ +package core + +import "testing" + +const ( + ownerID = 1 + otherID = 2 +) + +func repoWith(v Visibility) *Repo { + return &Repo{ID: 10, Name: "db", OwnerID: ownerID, OwnerName: "owner", Path: "/x", Visibility: v} +} + +func ptr(m AccessMode) *AccessMode { return &m } + +// grant bundles the four operation outcomes for one matrix cell. +type grant struct{ browse, clone, push, admin bool } + +func (g grant) want(op Op) bool { + switch op { + case OpBrowse: + return g.browse + case OpCloneRead: + return g.clone + case OpPush: + return g.push + case OpAdmin: + return g.admin + } + return false +} + +func TestAllowedMatrix(t *testing.T) { + anon := (*Caller)(nil) + user := &Caller{UserID: otherID, Username: "user", UserType: UserTypeUser} + owner := &Caller{UserID: ownerID, Username: "owner", UserType: UserTypeUser} + suspendedOwner := &Caller{UserID: ownerID, Username: "owner", UserType: UserTypeSuspended, Suspended: true} + suspendedUser := &Caller{UserID: otherID, Username: "user", UserType: UserTypeSuspended, Suspended: true} + + readOnly := grant{browse: true, clone: true} + readWrite := grant{browse: true, clone: true, push: true} + all := grant{browse: true, clone: true, push: true, admin: true} + none := grant{} + + tests := []struct { + name string + caller *Caller + repo *Repo + acl *AccessMode + want grant + }{ + // anonymous, no ACL + {"anon/public", anon, repoWith(VisibilityPublic), nil, readOnly}, + {"anon/unlisted", anon, repoWith(VisibilityUnlisted), nil, readOnly}, + {"anon/private", anon, repoWith(VisibilityPrivate), nil, none}, + + // authenticated non-owner, no ACL (same as anonymous) + {"user/public", user, repoWith(VisibilityPublic), nil, readOnly}, + {"user/unlisted", user, repoWith(VisibilityUnlisted), nil, readOnly}, + {"user/private", user, repoWith(VisibilityPrivate), nil, none}, + + // ACL RO grant: browse+clone on every visibility, incl. PRIVATE + {"acl-ro/public", user, repoWith(VisibilityPublic), ptr(AccessRO), readOnly}, + {"acl-ro/unlisted", user, repoWith(VisibilityUnlisted), ptr(AccessRO), readOnly}, + {"acl-ro/private", user, repoWith(VisibilityPrivate), ptr(AccessRO), readOnly}, + + // ACL RW grant: + push, never admin, on every visibility + {"acl-rw/public", user, repoWith(VisibilityPublic), ptr(AccessRW), readWrite}, + {"acl-rw/unlisted", user, repoWith(VisibilityUnlisted), ptr(AccessRW), readWrite}, + {"acl-rw/private", user, repoWith(VisibilityPrivate), ptr(AccessRW), readWrite}, + + // owner: everything on every visibility + {"owner/public", owner, repoWith(VisibilityPublic), nil, all}, + {"owner/unlisted", owner, repoWith(VisibilityUnlisted), nil, all}, + {"owner/private", owner, repoWith(VisibilityPrivate), nil, all}, + + // suspended: reads only, never push/admin + {"suspended-owner/private", suspendedOwner, repoWith(VisibilityPrivate), nil, readOnly}, + {"suspended-owner/public", suspendedOwner, repoWith(VisibilityPublic), nil, readOnly}, + {"suspended-user-rw/private", suspendedUser, repoWith(VisibilityPrivate), ptr(AccessRW), readOnly}, + {"suspended-user-rw/public", suspendedUser, repoWith(VisibilityPublic), ptr(AccessRW), readOnly}, + {"suspended-user-ro/private", suspendedUser, repoWith(VisibilityPrivate), ptr(AccessRO), readOnly}, + } + + ops := []Op{OpBrowse, OpCloneRead, OpPush, OpAdmin} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, op := range ops { + got := Allowed(tt.caller, tt.repo, tt.acl, op) + want := tt.want.want(op) + if got != want { + t.Errorf("Allowed(%s, %s) = %v, want %v", tt.name, op, got, want) + } + } + }) + } +} + +func TestAllowedUnknownOpDenied(t *testing.T) { + owner := &Caller{UserID: ownerID} + if Allowed(owner, repoWith(VisibilityPublic), nil, Op(99)) { + t.Fatal("unknown op must be denied even for the owner") + } +} + +func TestAllowedNilRepoDenied(t *testing.T) { + owner := &Caller{UserID: ownerID} + for _, op := range []Op{OpBrowse, OpCloneRead, OpPush, OpAdmin} { + if Allowed(owner, nil, nil, op) { + t.Fatalf("nil repo must deny %s", op) + } + } +} + +func TestNotFoundForPrivate(t *testing.T) { + anon := (*Caller)(nil) + user := &Caller{UserID: otherID} + owner := &Caller{UserID: ownerID} + + tests := []struct { + name string + caller *Caller + repo *Repo + acl *AccessMode + want bool + }{ + {"nil-repo", anon, nil, nil, true}, + {"public-anon", anon, repoWith(VisibilityPublic), nil, false}, + {"unlisted-anon", anon, repoWith(VisibilityUnlisted), nil, false}, + {"private-anon-hidden", anon, repoWith(VisibilityPrivate), nil, true}, + {"private-unauthorized-user-hidden", user, repoWith(VisibilityPrivate), nil, true}, + {"private-owner-visible", owner, repoWith(VisibilityPrivate), nil, false}, + {"private-acl-ro-visible", user, repoWith(VisibilityPrivate), ptr(AccessRO), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NotFoundForPrivate(tt.caller, tt.repo, tt.acl); got != tt.want { + t.Fatalf("NotFoundForPrivate(%s) = %v, want %v", tt.name, got, tt.want) + } + }) + } +} diff --git a/core/models.go b/core/models.go new file mode 100644 index 0000000000000000000000000000000000000000..235efeb6354e9bcf73e10fa00e48423eb82c9f65 --- /dev/null +++ b/core/models.go @@ -0,0 +1,85 @@ +// Package core holds the pure domain model for dolt.sr.ht: value types, +// name/path validation, and the access-control matrix. It performs no I/O and +// imports nothing outside the standard library, so every other package can +// depend on it freely. +package core + +// Visibility mirrors the Postgres `visibility` enum. +type Visibility string + +const ( + VisibilityPublic Visibility = "PUBLIC" + VisibilityUnlisted Visibility = "UNLISTED" + VisibilityPrivate Visibility = "PRIVATE" +) + +// AccessMode mirrors the Postgres `access_mode` enum: an ACL grant is either +// read-only (browse + clone) or read-write (+ push). +type AccessMode string + +const ( + AccessRO AccessMode = "RO" + AccessRW AccessMode = "RW" +) + +// UserType mirrors the Postgres `user_type` enum, itself a mirror of meta's +// user type. SUSPENDED users may read but never write. +type UserType string + +const ( + UserTypePending UserType = "PENDING" + UserTypeUser UserType = "USER" + UserTypeAdmin UserType = "ADMIN" + UserTypeSuspended UserType = "SUSPENDED" +) + +// Op is an operation whose authorization is decided by Allowed. +type Op int + +const ( + // OpBrowse is a read of repository metadata/contents through the web UI. + OpBrowse Op = iota + // OpCloneRead is a read over the remotesapi (clone/pull/fetch). + OpCloneRead + // OpPush is a write over the remotesapi (push). + OpPush + // OpAdmin is an owner-only administrative action (settings, ACLs, delete). + OpAdmin +) + +func (o Op) String() string { + switch o { + case OpBrowse: + return "browse" + case OpCloneRead: + return "clone-read" + case OpPush: + return "push" + case OpAdmin: + return "admin" + default: + return "unknown" + } +} + +// Repo is a hosted Dolt database. Path is the absolute on-disk NBS store dir. +type Repo struct { + ID int + Name string + Description string + OwnerID int + OwnerName string + Path string + Visibility Visibility +} + +// Caller is the authenticated principal for a request. A nil *Caller is an +// anonymous request. Suspended is carried explicitly (rather than derived from +// UserType) because the token/cookie resolver decides it, and it gates all +// write operations regardless of ACL. +type Caller struct { + UserID int + Username string + UserType UserType + Suspended bool +} diff --git a/core/names.go b/core/names.go new file mode 100644 index 0000000000000000000000000000000000000000..de665a8401dc55a6dcf76632dfa359e344f37771 --- /dev/null +++ b/core/names.go @@ -0,0 +1,68 @@ +package core + +import ( + "fmt" + "regexp" + "strings" +) + +// MaxNameLen is the maximum length of a database name. Names double as SQL +// database identifiers, so they are kept short and conservative. +const MaxNameLen = 64 + +// nameRe matches a valid database (or owner) name: an alphanumeric first and +// last character, with alphanumerics, hyphens and underscores in between. It +// structurally forbids "." and ".." (no dots at all) and leading/trailing +// separators. +var nameRe = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$`) + +// ValidateName reports whether name is an acceptable database name. It rejects +// the empty string, names longer than MaxNameLen, the traversal names "." and +// ".." explicitly, and anything not matching nameRe. +func ValidateName(name string) error { + if name == "" { + return fmt.Errorf("name must not be empty") + } + if name == "." || name == ".." { + return fmt.Errorf("name %q is not allowed", name) + } + if len(name) > MaxNameLen { + return fmt.Errorf("name is too long (%d > %d)", len(name), MaxNameLen) + } + if !nameRe.MatchString(name) { + return fmt.Errorf("name %q must match %s", name, nameRe.String()) + } + return nil +} + +// ParseRepoPath splits a repository path into its owner and database segments. +// It accepts "~user/db", "user/db", and leading/trailing slashes. It rejects +// empty segments, ".."/"." traversal segments, and any path that does not have +// exactly two segments. The returned owner never carries a leading "~". +// +// ParseRepoPath validates structure only; owner and db are not name-validated +// here (callers that touch disk or SQL must additionally run ValidateName). +func ParseRepoPath(path string) (owner, db string, err error) { + trimmed := strings.Trim(path, "/") + if trimmed == "" { + return "", "", fmt.Errorf("empty repo path") + } + segs := strings.Split(trimmed, "/") + if len(segs) != 2 { + return "", "", fmt.Errorf("repo path %q must have exactly 2 segments, got %d", path, len(segs)) + } + for _, s := range segs { + if s == "" { + return "", "", fmt.Errorf("repo path %q has an empty segment", path) + } + if s == "." || s == ".." { + return "", "", fmt.Errorf("repo path %q contains a traversal segment %q", path, s) + } + } + owner = strings.TrimPrefix(segs[0], "~") + db = segs[1] + if owner == "" { + return "", "", fmt.Errorf("repo path %q has an empty owner", path) + } + return owner, db, nil +} diff --git a/core/names_test.go b/core/names_test.go new file mode 100644 index 0000000000000000000000000000000000000000..85e41b4d30b9f993a0917f90b26315e04d0f3fba --- /dev/null +++ b/core/names_test.go @@ -0,0 +1,93 @@ +package core + +import ( + "strings" + "testing" +) + +func TestValidateName(t *testing.T) { + tests := []struct { + name string + input string + ok bool + }{ + {"simple", "demo", true}, + {"alnum", "db123", true}, + {"with-hyphen", "my-db", true}, + {"with-underscore", "my_db", true}, + {"mixed", "a-b_c-1", true}, + {"single-char", "a", true}, + {"single-digit", "7", true}, + {"max-length", strings.Repeat("a", MaxNameLen), true}, + + {"empty", "", false}, + {"dot", ".", false}, + {"dotdot", "..", false}, + {"embedded-dot", "a.b", false}, + {"leading-hyphen", "-db", false}, + {"trailing-hyphen", "db-", false}, + {"leading-underscore", "_db", false}, + {"trailing-underscore", "db_", false}, + {"slash", "a/b", false}, + {"space", "a b", false}, + {"tilde", "~db", false}, + {"too-long", strings.Repeat("a", MaxNameLen+1), false}, + {"unicode", "café", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateName(tt.input) + if tt.ok && err != nil { + t.Fatalf("ValidateName(%q) = %v, want nil", tt.input, err) + } + if !tt.ok && err == nil { + t.Fatalf("ValidateName(%q) = nil, want error", tt.input) + } + }) + } +} + +func TestParseRepoPath(t *testing.T) { + tests := []struct { + name string + input string + wantOwner string + wantDB string + wantErr bool + }{ + {"tilde", "~user/db", "user", "db", false}, + {"no-tilde", "user/db", "user", "db", false}, + {"leading-slash", "/~user/db", "user", "db", false}, + {"trailing-slash", "~user/db/", "user", "db", false}, + {"both-slashes", "/~user/db/", "user", "db", false}, + {"names-with-symbols", "~my-user/my_db-1", "my-user", "my_db-1", false}, + + {"empty", "", "", "", true}, + {"only-slashes", "///", "", "", true}, + {"one-segment", "~user", "", "", true}, + {"three-segments", "~user/db/extra", "", "", true}, + {"empty-inner-segment", "~user//db", "", "", true}, + {"traversal-dotdot", "~user/..", "", "", true}, + {"traversal-owner-dotdot", "../db", "", "", true}, + {"traversal-dot", "~user/.", "", "", true}, + {"tilde-only-owner", "~/db", "", "", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + owner, db, err := ParseRepoPath(tt.input) + if tt.wantErr { + if err == nil { + t.Fatalf("ParseRepoPath(%q) = (%q,%q,nil), want error", tt.input, owner, db) + } + return + } + if err != nil { + t.Fatalf("ParseRepoPath(%q) unexpected error: %v", tt.input, err) + } + if owner != tt.wantOwner || db != tt.wantDB { + t.Fatalf("ParseRepoPath(%q) = (%q,%q), want (%q,%q)", + tt.input, owner, db, tt.wantOwner, tt.wantDB) + } + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..aac397cd382d3c6c6b13d20b34af228bef691a74 --- /dev/null +++ b/go.mod @@ -0,0 +1,160 @@ +module go.bigb.es/sourcehut-dolt + +go 1.26.4 + +require ( + git.sr.ht/~bitfehler/brant v0.5.1 + git.sr.ht/~sircmpwn/core-go v0.0.0-20260520082310-fdb3662452dc + github.com/dolthub/dolt/go v0.40.5-0.20260626152440-45335d44ad79 + github.com/go-chi/chi/v5 v5.3.1 + github.com/lib/pq v1.10.9 + github.com/sirupsen/logrus v1.8.3 + google.golang.org/grpc v1.79.3 + gopkg.in/go-jose/go-jose.v2 v2.6.3 +) + +require ( + cel.dev/expr v0.25.1 // indirect + cloud.google.com/go v0.120.0 // indirect + cloud.google.com/go/auth v0.16.2 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect + cloud.google.com/go/storage v1.50.0 // indirect + git.sr.ht/~sircmpwn/dowork v0.0.0-20241216125407-2b00aa42322c // indirect + git.sr.ht/~sircmpwn/getopt v1.0.0 // indirect + git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9 // indirect + github.com/99designs/gqlgen v0.17.36 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 // indirect + github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/agnivade/levenshtein v1.1.1 // indirect + github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect + github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect + github.com/aws/aws-sdk-go-v2/config v1.29.8 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.64 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.41.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 // indirect + github.com/aws/smithy-go v1.24.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/cockroachdb/apd/v3 v3.2.3 // indirect + github.com/denisbrodbeck/machineid v1.0.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12 // indirect + github.com/dolthub/eventsapi_schema v0.0.0-20260310172945-37a9265ade69 // indirect + github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect + github.com/dolthub/fslock v0.0.5 // indirect + github.com/dolthub/go-icu-regex v0.0.0-20260610153742-72563bc7ca83 // indirect + github.com/dolthub/go-mysql-server v0.20.1-0.20260625171506-68aec8237480 // indirect + github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63 // indirect + github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect + github.com/dolthub/vitess v0.0.0-20260624214226-81d034e0fde8 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/edsrzf/mmap-go v1.2.0 // indirect + github.com/emersion/go-message v0.18.2 // indirect + github.com/emersion/go-pgpmail v0.2.2 // indirect + github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect + github.com/emersion/go-smtp v0.21.3 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/esote/minmaxheap v1.0.0 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fernet/fernet-go v0.0.0-20211208181803-9f70042a33ee // indirect + github.com/go-chi/cors v1.2.2 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.14.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d // indirect + github.com/kavu/go_reuseport v1.5.0 // indirect + github.com/kch42/buzhash v0.0.0-20160816060738-9bdec3dec7c6 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lestrrat-go/strftime v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mohae/uvarint v0.0.0-20160208145430-c3f9e62bf2b0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oracle/oci-go-sdk/v65 v65.55.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/sony/gobreaker v0.5.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec // indirect + github.com/vektah/gqlparser/v2 v2.5.8 // indirect + github.com/xtaci/smux v1.5.56 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/net v0.54.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.45.0 // indirect + google.golang.org/api v0.241.0 // indirect + google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..46461ca9d0e89f56e530573ff941cf6edeebd169 --- /dev/null +++ b/go.sum @@ -0,0 +1,515 @@ +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4= +cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= +cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.sr.ht/~bitfehler/brant v0.5.1 h1:emUbT5r1P0p2mgC/52NMAzGa2zw2To/Bim5ySeyGrks= +git.sr.ht/~bitfehler/brant v0.5.1/go.mod h1:XD6RmtKTOT/RL+2iQ+KBcPhLYAjeJWdLj3yfM5+7k/g= +git.sr.ht/~sircmpwn/core-go v0.0.0-20260520082310-fdb3662452dc h1:txQeGOaDKWVsAbnMd8CziM9GxbFn+6ztolA+sNE22T4= +git.sr.ht/~sircmpwn/core-go v0.0.0-20260520082310-fdb3662452dc/go.mod h1:JmathMemB+hBk1IgrMn6RuBGusd+fTg1s/X6rNVKXXU= +git.sr.ht/~sircmpwn/core-go v0.0.0-20260708091830-71b27871dc30 h1:Bh43WLyYzPHKKEsPeTNZTGGZP27TCmuuDUa+K7qVbp4= +git.sr.ht/~sircmpwn/core-go v0.0.0-20260708091830-71b27871dc30/go.mod h1:JmathMemB+hBk1IgrMn6RuBGusd+fTg1s/X6rNVKXXU= +git.sr.ht/~sircmpwn/dowork v0.0.0-20241216125407-2b00aa42322c h1:v2opuaN0C5ZpuCifRNR9ZQ8V9IG+Ja80otK1MFj5RnI= +git.sr.ht/~sircmpwn/dowork v0.0.0-20241216125407-2b00aa42322c/go.mod h1:8neHEO3503w/rNtttnR0JFpQgM/GFhaafVwvkPsFIDw= +git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= +git.sr.ht/~sircmpwn/getopt v1.0.0 h1:/pRHjO6/OCbBF4puqD98n6xtPEgE//oq5U8NXjP7ROc= +git.sr.ht/~sircmpwn/getopt v1.0.0/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= +git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9 h1:Ahny8Ud1LjVMMAlt8utUFKhhxJtwBAualvsbc/Sk7cE= +git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA= +github.com/99designs/gqlgen v0.17.36 h1:u/o/rv2SZ9s5280dyUOOrkpIIkr/7kITMXYD3rkJ9go= +github.com/99designs/gqlgen v0.17.36/go.mod h1:6RdyY8puhCoWAQVr2qzF2OMVfudQzc8ACxzpzluoQm4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 h1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4/go.mod h1:8mwH4klAm9DUgR2EEHyEEAQlRDvLPyg5fQry3y+cDew= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 h1:ig/FpDD2JofP/NExKQUbn7uOSZzJAQqogfqluZK4ed4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1eXdZyMw3F7uGA9qPn2J4+R8= +github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2/config v1.29.8 h1:RpwAfYcV2lr/yRc4lWhUM9JRPQqKgKWmou3LV7UfWP4= +github.com/aws/aws-sdk-go-v2/config v1.29.8/go.mod h1:t+G7Fq1OcO8cXTPPXzxQSnj/5Xzdc9jAAD3Xrn9/Mgo= +github.com/aws/aws-sdk-go-v2/credentials v1.18.2 h1:mfm0GKY/PHLhs7KO0sUaOtFnIQ15Qqxt+wXbO/5fIfs= +github.com/aws/aws-sdk-go-v2/credentials v1.18.2/go.mod h1:v0SdJX6ayPeZFQxgXUKw5RhLpAoZUuynxWDfh8+Eknc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 h1:owmNBboeA0kHKDcdF8KiSXmrIuXZustfMGGytv6OMkM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1/go.mod h1:Bg1miN59SGxrZqlP8vJZSmXW+1N8Y1MjQDq1OfuNod8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.64 h1:RTko0AQ0i1vWXDM97DkuW6zskgOxFxm4RqC0kmBJFkE= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.64/go.mod h1:ty968MpOa5CoQ/ALWNB8Gmfoehof2nRHDR/DZDPfimE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.41.0 h1:kSMAk72LZ5eIdY/W+tVV6VdokciajcDdVClEBVNWNP0= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.41.0/go.mod h1:yYaWRnVSPyAmexW5t7G3TcuYoalYfT+xQwzWsvtUQ7M= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15 h1:M1R1rud7HzDrfCdlBQ7NjnRsDNEhXO/vGhuD189Ggmk= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15/go.mod h1:uvFKBSq9yMPV4LGAi7N4awn4tLY+hKE35f8THes2mzQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 h1:uWaz3DoNK9MNhm7i6UGxqufwu3BEuJZm72WlpGwyVtY= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.1/go.mod h1:ILpVNjL0BO+Z3Mm0SbEeUoYS9e0eJWV1BxNppp0fcb8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 h1:XdG6/o1/ZDmn3wJU5SRAejHaWgKS4zHv0jBamuKuS2k= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1/go.mod h1:oiotGTKadCOCl3vg/tYh4k45JlDF81Ka8rdumNhEnIQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 h1:iF4Xxkc0H9c/K2dS0zZw3SCkj0Z7n6AMnUiiyoJND+I= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.1/go.mod h1:0bxIatfN0aLq4mjoLDeBpOjOke68OsFlXPDFJ7V0MYw= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cockroachdb/apd/v3 v3.2.3 h1:4Zx+I3R35bFXMnltzmjP79i2cravE4jTRL6ps9Aux80= +github.com/cockroachdb/apd/v3 v3.2.3/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= +github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12 h1:IdqX7J8vi/Kn3T3Ee0VzqnLqwFmgA2hr8WZETPcQjfM= +github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12/go.mod h1:rN7X8BHwkjPcfMQQ2QTAq/xM3leUSGLfb+1Js7Y6TVo= +github.com/dolthub/dolt/go v0.40.4 h1:9C6pjDN3G9YYSRcwG+Do9EiyiFIZVJMrnF4XWFy1EN4= +github.com/dolthub/dolt/go v0.40.4/go.mod h1:Tw9XAsW9VgEO+8h0qqXli6GAbxFvDoO3uDl61m0ldso= +github.com/dolthub/dolt/go v0.40.5-0.20260626152440-45335d44ad79 h1:jglYHtNhgm90ePaU8JQGREbI8oZXuMuZnocUF8047LI= +github.com/dolthub/dolt/go v0.40.5-0.20260626152440-45335d44ad79/go.mod h1:Pls391pPY0J/AojRBhmZchPRdQ9Ut4QU9x6p0bZ9/X4= +github.com/dolthub/eventsapi_schema v0.0.0-20260310172945-37a9265ade69 h1:JShhbqMw26nKx3pqqu/cFxOpzBkN+4elVhzuUfgDw2k= +github.com/dolthub/eventsapi_schema v0.0.0-20260310172945-37a9265ade69/go.mod h1:SSLraQS/jGLYFgff3vuZ+JbVUct6vyEeMzjLBqWqoyM= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= +github.com/dolthub/fslock v0.0.5 h1:QoXhBhgY1oumHE26qyE7tgmXUT8qjJwxsIzo54O/B/k= +github.com/dolthub/fslock v0.0.5/go.mod h1:sdofYYqE0D79zNZyB4/kmlnsQOVap1C2yByjGKSirEM= +github.com/dolthub/go-icu-regex v0.0.0-20260610153742-72563bc7ca83 h1:FEMjCGEroDnY/BXyAffVZxUpXhP2GpoUJyyq5KaLn8c= +github.com/dolthub/go-icu-regex v0.0.0-20260610153742-72563bc7ca83/go.mod h1:F3cnm+vMRK1HaU6+rNqQrOCyR03HHhR1GWG2gnPOqaE= +github.com/dolthub/go-mysql-server v0.20.1-0.20260625171506-68aec8237480 h1:LTH0FHVgS2Utgi/98xzfunuXl3dJckMAgM0RLUJAzjM= +github.com/dolthub/go-mysql-server v0.20.1-0.20260625171506-68aec8237480/go.mod h1:mj5/QX3V8i92REbA1w6CzyknJAFdKtdE7l931405C/E= +github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63 h1:OAsXLAPL4du6tfbBgK0xXHZkOlos63RdKYS3Sgw/dfI= +github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63/go.mod h1:lV7lUeuDhH5thVGDCKXbatwKy2KW80L4rMT46n+Y2/Q= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= +github.com/dolthub/vitess v0.0.0-20260624214226-81d034e0fde8 h1:zmKLyRCTiNZnizO++sNXP1QW2bxLE2Y+WJAdu/hcu6s= +github.com/dolthub/vitess v0.0.0-20260624214226-81d034e0fde8/go.mod h1:5SVEJgAhw5nnQUFnGgKI1Svqes1Mw+zKUsalyxmOuG0= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= +github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/emersion/go-message v0.17.0/go.mod h1:/9Bazlb1jwUNB0npYYBsdJ2EMOiiyN3m5UVHbY7GoNw= +github.com/emersion/go-message v0.18.2 h1:rl55SQdjd9oJcIoQNhubD2Acs1E6IzlZISRTK7x/Lpg= +github.com/emersion/go-message v0.18.2/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA= +github.com/emersion/go-pgpmail v0.2.2 h1:cO2jwsE0gb8aDdCcVH5Dfe1XV3Rhhw2GVWsmQd3CbaI= +github.com/emersion/go-pgpmail v0.2.2/go.mod h1:mRB5P7QKiAuOvcT36tdRZvm7nSt7V+f6jbzzup3HuvU= +github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY= +github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/emersion/go-smtp v0.21.3 h1:7uVwagE8iPYE48WhNsng3RRpCUpFvNl39JGNSIyGVMY= +github.com/emersion/go-smtp v0.21.3/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= +github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/esote/minmaxheap v1.0.0 h1:rgA7StnXXpZG6qlM0S7pUmEv1KpWe32rYT4x8J8ntaA= +github.com/esote/minmaxheap v1.0.0/go.mod h1:Ln8+i7fS1k3PLgZI2JAo0iA1as95QnIYiGCrqSJ5FZk= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fernet/fernet-go v0.0.0-20211208181803-9f70042a33ee h1:v6Eju/FhxsACGNipFEPBZZAzGr1F/jlRQr1qiBw2nEE= +github.com/fernet/fernet-go v0.0.0-20211208181803-9f70042a33ee/go.mod h1:2H9hjfbpSMHwY503FclkV/lZTBh2YlOmLLSda12uL8c= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= +github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= +github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kavu/go_reuseport v1.5.0 h1:UNuiY2OblcqAtVDE8Gsg1kZz8zbBWg907sP1ceBV+bk= +github.com/kavu/go_reuseport v1.5.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWoUx+JZ5/CU= +github.com/kch42/buzhash v0.0.0-20160816060738-9bdec3dec7c6 h1:l6Y3mFnF46A+CeZsTrT8kVIuhayq1266oxWpDKE7hnQ= +github.com/kch42/buzhash v0.0.0-20160816060738-9bdec3dec7c6/go.mod h1:UtDV9qK925GVmbdjR+e1unqoo+wGWNHHC6XB1Eu6wpE= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lestrrat-go/strftime v1.2.0 h1:8fAUYOeaJKCuLzNvUWBAo8t6I6hkFfodDTndEzJIun0= +github.com/lestrrat-go/strftime v1.2.0/go.mod h1:GtsIA/7ddIGJjEdfadUafEb1sbutvlvpMdPCMglykYo= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/uvarint v0.0.0-20160208145430-c3f9e62bf2b0 h1:fXRYk7YXVIBMGAHT+GmAcbiXrudXMPtqdLfbkVfUhkI= +github.com/mohae/uvarint v0.0.0-20160208145430-c3f9e62bf2b0/go.mod h1:+6ZKJfAk1B0oKLOwdzYuRVJn3upG1c7uOm5Ih7Rrkvc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oracle/oci-go-sdk/v65 v65.55.0 h1:enKyHVLdJYDJrc9232w33u5F6t2p8Din4593kn3nh/w= +github.com/oracle/oci-go-sdk/v65 v65.55.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.8.3 h1:DBBfY8eMYazKEJHb3JKpSPfpgd2mBCoNFlQx6C5fftU= +github.com/sirupsen/logrus v1.8.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= +github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks= +github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= +github.com/vektah/gqlparser/v2 v2.5.8 h1:pm6WOnGdzFOCfcQo9L3+xzW51mKrlwTEg4Wr7AH1JW4= +github.com/vektah/gqlparser/v2 v2.5.8/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +github.com/xtaci/smux v1.5.56 h1:Eyv/dUULmkGZZNucLUisnkzJ/4UQ5YZTschhugFBM0U= +github.com/xtaci/smux v1.5.56/go.mod h1:IGQ9QYrBphmb/4aTnLEcJby0TNr3NV+OslIOMrX825Q= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE= +google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= +gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/smoke/smoke.go b/internal/smoke/smoke.go new file mode 100644 index 0000000000000000000000000000000000000000..05013d535d8f4e088a9f8269baa13374c0835f6e --- /dev/null +++ b/internal/smoke/smoke.go @@ -0,0 +1,59 @@ +// Package smoke is a throwaway build-and-resolve smoke test for Phase 0. It +// imports every third-party package the project will depend on and references +// one exported symbol from each, so that `go build ./...` populates go.sum and +// proves the whole dependency set (core-go, dolthub/dolt, grpc, chi, pq, brant, +// logrus, go-jose) resolves and compiles together — including the CGO (gozstd) +// pieces of dolt. +// +// DELETE THIS PACKAGE in the final phase (Phase 3); it exists only to pin and +// verify the dependency graph and has no runtime purpose. +package smoke + +import ( + "git.sr.ht/~bitfehler/brant" + "git.sr.ht/~sircmpwn/core-go/auth" + "git.sr.ht/~sircmpwn/core-go/config" + "git.sr.ht/~sircmpwn/core-go/crypto" + "git.sr.ht/~sircmpwn/core-go/database" + "git.sr.ht/~sircmpwn/core-go/server" + "github.com/dolthub/dolt/go/libraries/doltcore/creds" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" + "github.com/dolthub/dolt/go/libraries/doltcore/remotesrv" + "github.com/dolthub/dolt/go/libraries/utils/earl" + "github.com/dolthub/dolt/go/libraries/utils/filesys" + "github.com/dolthub/dolt/go/store/nbs" + "github.com/dolthub/dolt/go/store/types" + "github.com/go-chi/chi/v5" + _ "github.com/lib/pq" // registers the "postgres" database/sql driver + "github.com/sirupsen/logrus" + "google.golang.org/grpc" + jose "gopkg.in/go-jose/go-jose.v2" + + remotesapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/remotesapi/v1alpha1" +) + +// refs pins one exported symbol from each key package. The values are never +// used; the point is that the compiler resolves the identifiers. +var refs = []any{ + // core-go + server.New, + auth.DecodeBearerToken, + config.LoadConfig, + crypto.InitCrypto, + database.Middleware, + // dolthub/dolt + remotesrv.NewServer, + doltdb.LoadDoltDB, + nbs.NewUnlimitedMemQuotaProvider, + earl.FileUrlFromPath, + creds.PubKeyToKIDStr, + filesys.LocalFS, + types.Format_DOLT, + remotesapi.PushConcurrencyControl_PUSH_CONCURRENCY_CONTROL_IGNORE_WORKING_SET, + // transport / web / db / migrations / logging / jose + grpc.NewServer, + chi.NewRouter, + brant.NewProvider, + logrus.New, + jose.NewSigner, +} diff --git a/migrations/0001_initial.sql b/migrations/0001_initial.sql new file mode 100644 index 0000000000000000000000000000000000000000..76e0272130a2ef98764854eae3bbe7669721d437 --- /dev/null +++ b/migrations/0001_initial.sql @@ -0,0 +1,73 @@ +-- +brant Up +CREATE TYPE visibility AS ENUM ( + 'PUBLIC', + 'PRIVATE', + 'UNLISTED' +); + +CREATE TYPE user_type AS ENUM ( + 'PENDING', + 'USER', + 'ADMIN', + 'SUSPENDED' +); + +CREATE TYPE access_mode AS ENUM ( + 'RO', + 'RW' +); + +CREATE TABLE "user" ( + id integer PRIMARY KEY, + username varchar(256) UNIQUE, + created timestamp NOT NULL, + updated timestamp NOT NULL, + email varchar(256) NOT NULL UNIQUE, + user_type user_type NOT NULL, + url varchar(256), + location varchar(256), + bio varchar(4096), + suspension_notice varchar(4096) +); +CREATE INDEX ix_user_username ON "user"(username); + +CREATE TABLE repository ( + id serial PRIMARY KEY, + created timestamp NOT NULL, + updated timestamp NOT NULL, + name varchar(64) NOT NULL, + description varchar(1024), + owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, + path varchar(1024) NOT NULL UNIQUE, + visibility visibility NOT NULL, + CONSTRAINT uq_repo_owner_id_name UNIQUE (owner_id, name) +); + +CREATE TABLE access ( + id serial PRIMARY KEY, + created timestamp NOT NULL, + updated timestamp NOT NULL, + repo_id integer NOT NULL REFERENCES repository(id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, + mode access_mode NOT NULL, + CONSTRAINT uq_access_user_id_repo_id UNIQUE (user_id, repo_id) +); + +CREATE TABLE dolt_key ( + id serial PRIMARY KEY, + created timestamp NOT NULL, + user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, + kid varchar(64) NOT NULL UNIQUE, + pubkey bytea NOT NULL, + comment varchar(256), + last_used timestamp +); + +-- +brant Down +DROP TABLE dolt_key; +DROP TABLE access; +DROP TABLE repository; +DROP TABLE "user"; +DROP TYPE access_mode; +DROP TYPE user_type; +DROP TYPE visibility; diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..8653ff5e5a61425b7d0e24684537166e0704c94d --- /dev/null +++ b/schema.sql @@ -0,0 +1,70 @@ +-- dolt.sr.ht full initial schema. +-- +-- This is the authoritative DDL for a fresh install. The brant migration in +-- migrations/0001_initial.sql applies the same objects incrementally; keep the +-- two in sync. The "user" table mirrors meta's users (core-go's +-- FetchMetaProfile lazily inserts rows keyed by meta's user id). + +CREATE TYPE visibility AS ENUM ( + 'PUBLIC', + 'PRIVATE', + 'UNLISTED' +); + +CREATE TYPE user_type AS ENUM ( + 'PENDING', + 'USER', + 'ADMIN', + 'SUSPENDED' +); + +CREATE TYPE access_mode AS ENUM ( + 'RO', + 'RW' +); + +CREATE TABLE "user" ( + id integer PRIMARY KEY, -- meta's user id, inserted explicitly (not serial) + username varchar(256) UNIQUE, + created timestamp NOT NULL, + updated timestamp NOT NULL, + email varchar(256) NOT NULL UNIQUE, + user_type user_type NOT NULL, + url varchar(256), + location varchar(256), + bio varchar(4096), + suspension_notice varchar(4096) +); +CREATE INDEX ix_user_username ON "user"(username); + +CREATE TABLE repository ( + id serial PRIMARY KEY, + created timestamp NOT NULL, + updated timestamp NOT NULL, + name varchar(64) NOT NULL, + description varchar(1024), + owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, + path varchar(1024) NOT NULL UNIQUE, -- absolute on-disk NBS store dir + visibility visibility NOT NULL, + CONSTRAINT uq_repo_owner_id_name UNIQUE (owner_id, name) +); + +CREATE TABLE access ( + id serial PRIMARY KEY, + created timestamp NOT NULL, + updated timestamp NOT NULL, + repo_id integer NOT NULL REFERENCES repository(id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, + mode access_mode NOT NULL, + CONSTRAINT uq_access_user_id_repo_id UNIQUE (user_id, repo_id) +); + +CREATE TABLE dolt_key ( + id serial PRIMARY KEY, + created timestamp NOT NULL, + user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, + kid varchar(64) NOT NULL UNIQUE, -- base32(SHA-512/224(pubkey)), dolt alphabet + pubkey bytea NOT NULL, -- 32-byte ed25519 public key + comment varchar(256), + last_used timestamp +); diff --git a/scss/main.scss b/scss/main.scss new file mode 100644 index 0000000000000000000000000000000000000000..e4fec7f8c7500edeb696d1c20d2467f56ef6c18e --- /dev/null +++ b/scss/main.scss @@ -0,0 +1,26 @@ +@import "base"; + +// Minimal dolt.sr.ht service styles layered on the shared core.sr.ht theme. +// The bulk of the look comes from @import "base" (Bootstrap + sourcehut vars), +// resolved via `sassc -I $(ASSETS)/scss`. + +.header-extension { + margin-top: -1rem; + margin-bottom: 1rem; +} + +// Clone box on the database overview page: two variants (token / keypair). +.clone-url { + font-family: $font-family-monospace; + input { + font-family: $font-family-monospace; + } +} + +// Compact grid for the database list on user and dashboard pages. +.repo-list { + display: grid; + grid-template-columns: 1fr auto; + grid-gap: 0.25rem 1rem; + align-items: baseline; +} diff --git a/static/logo.svg b/static/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..953592414791428a927096b765e30eac02fe096a --- /dev/null +++ b/static/logo.svg @@ -0,0 +1,14 @@ + + + + + diff --git a/storage/spike_test.go b/storage/spike_test.go new file mode 100644 index 0000000000000000000000000000000000000000..47ef1a7e0e188a07b5cb95774e9cb6781a25acf6 --- /dev/null +++ b/storage/spike_test.go @@ -0,0 +1,220 @@ +//go:build spike + +// Phase-0 de-risk spike. This is the whole point of Phase 0: prove that the +// pinned github.com/dolthub/dolt/go module can init a bare NBS store, serve it +// over remotesrv, and round-trip a real `dolt` CLI (v2.1.10) clone -> insert -> +// commit -> push -> re-clone. If this passes, the storage format and remotesapi +// RPCs are compatible between the module and the CLI, and the parallel waves +// can proceed. +// +// Run with: +// +// CGO_CPPFLAGS=-I/opt/homebrew/opt/icu4c@78/include \ +// CGO_LDFLAGS=-L/opt/homebrew/opt/icu4c@78/lib \ +// go test -tags spike ./storage/ -run TestSpike -v +// +// The test skips (does not fail) when the dolt CLI is absent. +package storage + +import ( + "context" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + remotesapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/remotesapi/v1alpha1" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" + "github.com/dolthub/dolt/go/libraries/doltcore/remotesrv" + "github.com/dolthub/dolt/go/libraries/utils/earl" + "github.com/dolthub/dolt/go/libraries/utils/filesys" + "github.com/dolthub/dolt/go/store/nbs" + "github.com/dolthub/dolt/go/store/types" + "github.com/sirupsen/logrus" +) + +const doltBin = "/opt/homebrew/bin/dolt" + +// spikeCache is a minimal remotesrv.DBCache modeled on the upstream +// utils/remotesrv LocalCSCache: it memoizes one nbs.NewLocalStore per repo +// path, resolved under a fixed root directory. Real storage/ (wave A) will +// grow repo-row validation and eviction on top of this shape. +type spikeCache struct { + mu sync.Mutex + root string + dbs map[string]remotesrv.RemoteSrvStore +} + +func (c *spikeCache) Get(ctx context.Context, path, nbfVerStr string) (remotesrv.RemoteSrvStore, error) { + c.mu.Lock() + defer c.mu.Unlock() + + id := filepath.FromSlash(strings.Trim(path, "/")) + if cs, ok := c.dbs[id]; ok { + return cs, nil + } + abs := filepath.Join(c.root, id) + if err := os.MkdirAll(abs, 0o755); err != nil { + return nil, err + } + cs, err := nbs.NewLocalStore(ctx, nbfVerStr, abs, 128*1024*1024, nbs.NewUnlimitedMemQuotaProvider(), false) + if err != nil { + return nil, err + } + c.dbs[id] = cs + return cs, nil +} + +func TestSpike(t *testing.T) { + if _, err := os.Stat(doltBin); err != nil { + t.Skipf("dolt CLI not found at %s (%v); skipping interop spike", doltBin, err) + } + + ctx := context.Background() + root := t.TempDir() + reposRoot := filepath.Join(root, "repos") + + // 1. Create a bare NBS store at repos/test/db and write an empty repo. + storeDir := filepath.Join(reposRoot, "test", "db") + if err := os.MkdirAll(storeDir, 0o755); err != nil { + t.Fatal(err) + } + fileURL := earl.FileUrlFromPath(storeDir, os.PathSeparator) + ddb, err := doltdb.LoadDoltDB(ctx, types.Format_DOLT, fileURL, filesys.LocalFS) + if err != nil { + t.Fatalf("LoadDoltDB(%s): %v", fileURL, err) + } + if err := ddb.WriteEmptyRepo(ctx, "main", "spike", "spike@test.local"); err != nil { + t.Fatalf("WriteEmptyRepo: %v", err) + } + // Release the init handle before the server opens its own store over the + // same directory. + if err := ddb.Close(); err != nil { + t.Fatalf("close init DoltDB: %v", err) + } + + // 2. Serve repos/ over remotesrv on a single ephemeral localhost port + // (http + gRPC multiplexed), no auth interceptors, plain http. + addr := freeAddr(t) + cache := &spikeCache{root: reposRoot, dbs: map[string]remotesrv.RemoteSrvStore{}} + logger := logrus.New() + logger.SetLevel(logrus.ErrorLevel) + // The server FS must be rooted at reposRoot so chunk-download URL prefixes + // are clean relatives (e.g. "test/db") rather than "../../.." paths, which + // the sealed-URL file handler rejects. Upstream achieves this by chdir-ing + // to the repos root; we set the FS working dir instead to avoid mutating + // global process state. + fs, err := filesys.LocalFilesysWithWorkingDir(reposRoot) + if err != nil { + t.Fatalf("LocalFilesysWithWorkingDir(%s): %v", reposRoot, err) + } + server, err := remotesrv.NewServer(remotesrv.ServerArgs{ + Logger: logrus.NewEntry(logger), + HttpHost: "", // echo request :authority into chunk URLs + HttpListenAddr: addr, + GrpcListenAddr: addr, // == HttpListenAddr => single-port multiplex + FS: fs, + DBCache: cache, + ConcurrencyControl: remotesapi.PushConcurrencyControl_PUSH_CONCURRENCY_CONTROL_IGNORE_WORKING_SET, + }) + if err != nil { + t.Fatalf("remotesrv.NewServer: %v", err) + } + listeners, err := server.Listeners() + if err != nil { + t.Fatalf("server.Listeners: %v", err) + } + go server.Serve(listeners) + defer server.GracefulStop() + waitTCP(t, addr) + + // 3. Drive the real dolt CLI against the server in an isolated HOME so the + // developer's real dolt config is untouched. + home := filepath.Join(root, "home") + if err := os.MkdirAll(home, 0o755); err != nil { + t.Fatal(err) + } + env := append(os.Environ(), "HOME="+home) + runDolt(t, env, root, "config", "--global", "--add", "user.email", "spike@test.local") + runDolt(t, env, root, "config", "--global", "--add", "user.name", "spike") + + url := fmt.Sprintf("http://%s/test/db", addr) + + clone1 := filepath.Join(root, "clone1") + if err := os.MkdirAll(clone1, 0o755); err != nil { + t.Fatal(err) + } + runDolt(t, env, clone1, "clone", url) + + work := filepath.Join(clone1, "db") + runDolt(t, env, work, "sql", "-q", + "create table t(i int primary key); insert into t values (1),(2),(3);") + runDolt(t, env, work, "commit", "-Am", "spike commit") + runDolt(t, env, work, "push", "origin", "main") + + // 4. Fresh re-clone and verify the pushed rows are present. + clone2 := filepath.Join(root, "clone2") + if err := os.MkdirAll(clone2, 0o755); err != nil { + t.Fatal(err) + } + runDolt(t, env, clone2, "clone", url) + out := runDolt(t, env, filepath.Join(clone2, "db"), "sql", "-q", + "select i from t order by i", "-r", "csv") + + for _, want := range []string{"1", "2", "3"} { + if !strings.Contains(out, want) { + t.Fatalf("re-cloned db missing row %q; got csv:\n%s", want, out) + } + } + t.Logf("spike round-trip OK; re-clone csv:\n%s", out) +} + +// freeAddr returns a currently-free 127.0.0.1 address. There is an inherent +// race between closing the probe listener and the server binding, but it is +// acceptable for a single local test. +func freeAddr(t *testing.T) string { + t.Helper() + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("probe listen: %v", err) + } + addr := l.Addr().String() + if err := l.Close(); err != nil { + t.Fatalf("probe close: %v", err) + } + return addr +} + +// waitTCP blocks until addr accepts a TCP connection or the deadline elapses. +func waitTCP(t *testing.T, addr string) { + t.Helper() + deadline := time.Now().Add(10 * time.Second) + for time.Now().Before(deadline) { + conn, err := net.DialTimeout("tcp", addr, 200*time.Millisecond) + if err == nil { + conn.Close() + return + } + time.Sleep(50 * time.Millisecond) + } + t.Fatalf("server at %s never became reachable", addr) +} + +// runDolt runs the dolt CLI in dir with env, fails the test on non-zero exit, +// and returns combined stdout+stderr. +func runDolt(t *testing.T, env []string, dir string, args ...string) string { + t.Helper() + cmd := exec.Command(doltBin, args...) + cmd.Dir = dir + cmd.Env = env + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("dolt %s failed: %v\n%s", strings.Join(args, " "), err, out) + } + return string(out) +}