tidy codes

Kim committed Mar 30, 2026 at 16:16 UTC 541c56ffbc0eb605c1e0b13dd50e820d26c7a7ec
13 files changed +46 -78
CLAUDE.md renamed
docs/AGENTS.md deleted
-33
@@ -1,33 +0,0 @@
1 -# AGENTS.md
2 -
3 -Keep this file short and behavioral.
4 -Architecture, product behavior, and design rationale belong in `docs/architecture.md` and `docs/adr/README.md`.
5 -
6 -## Development Principles
7 -
8 -- Minimizing concepts, duplication, and ceremony.
9 -- Prefer a single stable contract with one real owner.
10 -- Prefer local simplicity over premature or speculative abstraction.
11 -- Add indirection only when it removes real coupling or protects a real boundary.
12 -- Tests should protect stable contracts and invariants, not drive the spec.
13 -
14 -## Project Principles
15 -
16 -- When caller and callee are both local and no real boundary exists, change both directly; do not preserve local call shapes.
17 -- If a field, method, wrapper, or abstraction has no clear, current use and does not protect a real boundary, remove it immediately.
18 -- No wrapper functions or helpers unless they remove real coupling or protect a real boundary.
19 -- Prefer direct code over layers, facades, and indirection.
20 -- Prefer flattening and merging nearby responsibilities over splitting by default.
21 -- Remove dead fields, methods, config, and stale state while touching nearby code.
22 -- Do not duplicate normalization, validation, or defaulting logic; keep it in a single real owner.
23 -- Keep shared stateless transforms in `utils/`; keep stateful and domain-shaped logic with the real owner.
24 -- Keep stable shared contracts, constants, and public paths in `types/`, not in runtime or helpers.
25 -- Resolve complexity in the lowest coherent owner and expose only the minimum surface upward.
26 -- Shared runtime logic must live in one real owner and be reused, not mirrored.
27 -
28 -## Verification
29 -
30 -- CI commands: `make vet`, `make lint`, `make test`, `make vuln`.
31 -- `make tidy` is local maintenance, not a CI requirement.
32 -- Run tests only when explicitly requested.
33 -- If verification seems necessary, ask before running it.
docs/architecture.md
+23
@@ -42,6 +42,7 @@ UDP client
42 - `/sdk/register` is authenticated by a SIWE challenge/response flow using the SDK owner secp256k1 key. On success, the relay issues a lease-scoped ES256K JWT access token signed by the relay owner key and used for the rest of the lease lifecycle.
43 - Relay URLs must use `https://`.
44 - HTTP/2 stays disabled on the admin/API TLS listener because `/sdk/connect` depends on HTTP/1.1 hijacking semantics.
45 +- WireGuard, when enabled, is relay-to-relay overlay transport only. It is not used for tenant stream TLS, public UDP ingress, or `/sdk/*` control-plane traffic.
46
47 ### Reverse Session Protocol
48
@@ -112,6 +113,7 @@ That distinction matters because `/sdk/connect` stops being ordinary HTTP once h
113 - `keyless`: admin/API TLS attach helpers and tenant-side signer integration
114 - `auth`: SIWE register challenge creation/verification plus lease access token issue/verify
115 - `discovery`: signed relay descriptor publication and relay-set synchronization
116 +- `wireguard`: optional relay overlay network used to reach peer relay APIs over internal overlay IPs and keep relay peer state synchronized
117 - `Server` additionally owns `quicTunnel` (QUIC listener, ALPN `portal-tunnel`) when UDP transport is enabled
118
119 ### SDK (`sdk/`)
@@ -198,6 +200,26 @@ Client --UDP--> [:50000+ Relay] --DATAGRAM--> [RelayDatagram] --QUIC--> [ClientD
200
201 Wire format (`types/transport.go`): `[flowID uvarint][payload bytes]`
202
203 +## WireGuard Overlay and Discovery
204 +
205 +- Discovery starts from bootstrap relay URLs over normal public HTTPS.
206 +- Each relay publishes a signed descriptor that may advertise:
207 + - `wireguard_public_key`
208 + - `wireguard_endpoint`
209 + - `overlay_ipv4`
210 + - optional `overlay_cidrs`
211 +- When discovery is enabled and the relay has a WireGuard private key, the relay creates an internal overlay interface and derives:
212 + - a relay WireGuard public key
213 + - an overlay IPv4 identity
214 + - a peer API listener bound on the overlay address
215 +- The overlay peer API is plain HTTP on the WireGuard network, not public Internet HTTP. It serves the same discovery payload shape used by public `/discovery`.
216 +- Bootstrap relays are discovered first over public HTTPS. Non-bootstrap relays that advertise overlay support become sync candidates and are polled again over the WireGuard overlay.
217 +- Relay-set snapshots are translated into WireGuard peers with:
218 + - peer public key
219 + - endpoint
220 + - allowed IPs = peer overlay `/32` plus advertised overlay CIDRs
221 +- Overlay failure affects inter-relay discovery and mesh synchronization only. Tenant stream routing, keyless TLS, register/renew/connect, and public UDP ingress do not depend on the WireGuard transport path directly.
222 +
223 ## Control Plane Flow
224
225 ### 1. Register
@@ -338,6 +360,7 @@ Relay-local frontend asset filenames stay in `cmd/relay-server`, not `types/`.
360 - Reverse-only backend connectivity
361 - One canonical raw TCP reverse transport
362 - Raw public UDP exposure with an internal QUIC datagram backhaul
363 +- Optional WireGuard relay overlay for relay discovery and peer synchronization
364 - SNI-based routing with root-host fallback
365 - End-to-end tenant TLS with relay-backed keyless signing
366 - Traffic-triggered detect-only MITM self-probing for probable relay-side TLS termination
portal/api_server.go
+3 -6
@@ -141,9 +141,6 @@ func (s *Server) handleRelayDiscovery(w http.ResponseWriter, r *http.Request) {
141 SupportsTCP: true,
142 SupportsUDP: s.cfg.UDPPortCount > 0,
143 SupportsOverlayPeer: supportsOverlayPeer,
144 - SupportsWitness: false,
145 - SupportsVPNExit: false,
146 - StatusState: "healthy",
144 WireGuardPublicKey: strings.TrimSpace(s.wgConfig.PublicKey),
145 WireGuardEndpoint: strings.TrimSpace(s.wgConfig.Endpoint),
146 OverlayIPv4: strings.TrimSpace(s.wgConfig.OverlayIPv4),
@@ -155,7 +152,7 @@ func (s *Server) handleRelayDiscovery(w http.ResponseWriter, r *http.Request) {
152 }
153
154 resp := types.DiscoveryResponse{
158 - ProtocolVersion: 1,
155 + ProtocolVersion: types.ProtocolVersion,
156 GeneratedAt: now,
157 Self: self,
158 Relays: nil,
@@ -175,8 +172,8 @@ func (s *Server) handleDomain(w http.ResponseWriter, r *http.Request) {
172 }
173
174 utils.WriteAPIData(w, http.StatusOK, types.DomainResponse{
178 - SDKVersion: types.SDKProtocolVersion,
179 - ReleaseVersion: types.ReleaseVersion,
175 + ProtocolVersion: types.ProtocolVersion,
176 + ReleaseVersion: types.ReleaseVersion,
177 })
178 }
179
portal/discovery/discovery.go
+4 -3
@@ -32,9 +32,6 @@ func NormalizeDescriptor(desc types.RelayDescriptor) (types.RelayDescriptor, err
32 if !desc.ExpiresAt.IsZero() {
33 desc.ExpiresAt = desc.ExpiresAt.UTC()
34 }
35 - if !desc.LastMITMDetectedAt.IsZero() {
36 - desc.LastMITMDetectedAt = desc.LastMITMDetectedAt.UTC()
37 - }
35
36 if desc.APIHTTPSAddr != "" {
37 normalized, err := utils.NormalizeRelayURL(desc.APIHTTPSAddr)
@@ -157,6 +154,10 @@ func ValidateDescriptor(desc types.RelayDescriptor, now time.Time) (types.RelayD
154 }
155
156 func ValidateRelayDiscoveryResponse(resp types.DiscoveryResponse, now time.Time) (types.RelayDescriptor, []types.RelayDescriptor, error) {
157 + if strings.TrimSpace(resp.ProtocolVersion) != types.ProtocolVersion {
158 + return types.RelayDescriptor{}, nil, fmt.Errorf("relay protocol version mismatch: relay=%q client=%q", strings.TrimSpace(resp.ProtocolVersion), types.ProtocolVersion)
159 + }
160 +
161 self, err := ValidateDescriptor(resp.Self, now)
162 if err != nil {
163 return types.RelayDescriptor{}, nil, err
portal/server_test.go
+3 -4
@@ -53,7 +53,6 @@ func mustSignedRelayDescriptor(t *testing.T, ownerPrivateKey, relayURL string) t
53 OverlayIPv4: overlayIPv4,
54 SupportsTCP: true,
55 SupportsOverlayPeer: true,
56 - StatusState: "healthy",
56 }, identity.PrivateKey)
57 if err != nil {
58 t.Fatalf("SignedDescriptor() error = %v", err)
@@ -395,7 +394,7 @@ func TestServerRecordVerifiedDiscoveryPeerRequiresDirectConfirmation(t *testing.
394 resultUpdated, resultAdded, warnErr, err := applyDiscovery(
395 bootstrapDesc.RelayID,
396 bootstrapDesc.APIHTTPSAddr,
398 - types.DiscoveryResponse{Self: bootstrapDesc},
397 + types.DiscoveryResponse{ProtocolVersion: types.ProtocolVersion, Self: bootstrapDesc},
398 false,
399 )
400 if err != nil {
@@ -414,7 +413,7 @@ func TestServerRecordVerifiedDiscoveryPeerRequiresDirectConfirmation(t *testing.
413 resultUpdated, resultAdded, warnErr, err = applyDiscovery(
414 bootstrapDesc.RelayID,
415 bootstrapDesc.APIHTTPSAddr,
417 - types.DiscoveryResponse{Self: bootstrapDesc, Relays: []types.RelayDescriptor{relayADesc}},
416 + types.DiscoveryResponse{ProtocolVersion: types.ProtocolVersion, Self: bootstrapDesc, Relays: []types.RelayDescriptor{relayADesc}},
417 false,
418 )
419 if err != nil {
@@ -485,7 +484,7 @@ func TestServerRecordVerifiedDiscoveryPeerRequiresDirectConfirmation(t *testing.
484 resultUpdated, resultAdded, warnErr, err = applyDiscovery(
485 relayADesc.RelayID,
486 relayADesc.APIHTTPSAddr,
488 - types.DiscoveryResponse{Self: relayADesc},
487 + types.DiscoveryResponse{ProtocolVersion: types.ProtocolVersion, Self: relayADesc},
488 true,
489 )
490 if err != nil {
sdk/api_client.go
+2 -2
@@ -196,8 +196,8 @@ func (a *apiClient) ensureCompatible(ctx context.Context, httpClient *http.Clien
196 }
197 return fmt.Errorf("%w: %w", errRelayIncompatible, err)
198 }
199 - if strings.TrimSpace(resp.SDKVersion) != types.SDKProtocolVersion {
200 - return fmt.Errorf("%w: relay sdk version mismatch: relay=%q client=%q", errRelayIncompatible, strings.TrimSpace(resp.SDKVersion), types.SDKProtocolVersion)
199 + if strings.TrimSpace(resp.ProtocolVersion) != types.ProtocolVersion {
200 + return fmt.Errorf("%w: relay protocol version mismatch: relay=%q client=%q", errRelayIncompatible, strings.TrimSpace(resp.ProtocolVersion), types.ProtocolVersion)
201 }
202 return nil
203 }
sdk/expose_test.go
+3 -4
@@ -29,7 +29,6 @@ func mustSignedRelayDescriptor(t *testing.T, ownerPrivateKey, relayID, relayURL
29 IssuedAt: now,
30 ExpiresAt: now.Add(time.Hour),
31 APIHTTPSAddr: relayURL,
32 - StatusState: "healthy",
32 }, identity.PrivateKey)
33 if err != nil {
34 t.Fatalf("SignedDescriptor() error = %v", err)
@@ -174,18 +173,18 @@ func TestExposurePinDiscoveredDescriptorRejectsIdentityChange(t *testing.T) {
173 exposure := &Exposure{relaySet: discovery.NewRelaySet()}
174 desc := mustSignedRelayDescriptor(t, strings.Repeat("11", 32), "relay-a", "https://relay-a.example")
175
177 - if _, _, _, _, err := exposure.relaySet.ApplyRelayDiscoveryResponse(desc.RelayID, desc.APIHTTPSAddr, types.DiscoveryResponse{Self: desc}, time.Now().UTC()); err != nil {
176 + if _, _, _, _, err := exposure.relaySet.ApplyRelayDiscoveryResponse(desc.RelayID, desc.APIHTTPSAddr, types.DiscoveryResponse{ProtocolVersion: types.ProtocolVersion, Self: desc}, time.Now().UTC()); err != nil {
177 t.Fatalf("ApplyRelayDiscoveryResponse() error = %v", err)
178 }
179
180 changedSigner := mustSignedRelayDescriptor(t, strings.Repeat("12", 32), desc.RelayID, desc.APIHTTPSAddr)
182 - _, _, _, _, err := exposure.relaySet.ApplyRelayDiscoveryResponse(desc.RelayID, desc.APIHTTPSAddr, types.DiscoveryResponse{Self: changedSigner}, time.Now().UTC())
181 + _, _, _, _, err := exposure.relaySet.ApplyRelayDiscoveryResponse(desc.RelayID, desc.APIHTTPSAddr, types.DiscoveryResponse{ProtocolVersion: types.ProtocolVersion, Self: changedSigner}, time.Now().UTC())
182 if err == nil {
183 t.Fatal("ApplyRelayDiscoveryResponse() error = nil, want pinned signer mismatch")
184 }
185
186 changedURL := mustSignedRelayDescriptor(t, strings.Repeat("11", 32), desc.RelayID, "https://relay-b.example")
188 - _, _, _, _, err = exposure.relaySet.ApplyRelayDiscoveryResponse(desc.RelayID, "", types.DiscoveryResponse{Self: changedURL}, time.Now().UTC())
187 + _, _, _, _, err = exposure.relaySet.ApplyRelayDiscoveryResponse(desc.RelayID, "", types.DiscoveryResponse{ProtocolVersion: types.ProtocolVersion, Self: changedURL}, time.Now().UTC())
188 if err == nil {
189 t.Fatal("ApplyRelayDiscoveryResponse() error = nil, want pinned relay url mismatch")
190 }
sdk/sdk_test.go
+3 -3
@@ -24,7 +24,7 @@ func TestNewListenerRegistersLeaseWithMainContract(t *testing.T) {
24 writeSDKTestEnvelope(w, http.StatusOK, types.APIEnvelope[types.DomainResponse]{
25 OK: true,
26 Data: types.DomainResponse{
27 - SDKVersion: types.SDKProtocolVersion,
27 + ProtocolVersion: types.ProtocolVersion,
28 },
29 })
30 case types.PathSDKRegisterChallenge:
@@ -176,7 +176,7 @@ func TestExposeResolvesOwnerPrivateKey(t *testing.T) {
176 writeSDKTestEnvelope(w, http.StatusOK, types.APIEnvelope[types.DomainResponse]{
177 OK: true,
178 Data: types.DomainResponse{
179 - SDKVersion: types.SDKProtocolVersion,
179 + ProtocolVersion: types.ProtocolVersion,
180 },
181 })
182 case types.PathSDKRegisterChallenge:
@@ -260,7 +260,7 @@ func TestExposeGeneratesOwnerAddressWithoutPrivateKey(t *testing.T) {
260 writeSDKTestEnvelope(w, http.StatusOK, types.APIEnvelope[types.DomainResponse]{
261 OK: true,
262 Data: types.DomainResponse{
263 - SDKVersion: types.SDKProtocolVersion,
263 + ProtocolVersion: types.ProtocolVersion,
264 },
265 })
266 case types.PathSDKRegisterChallenge:
types/api.go
+3 -3
@@ -85,7 +85,7 @@ type RegisterResponse struct {
85 }
86
87 type DiscoveryResponse struct {
88 - ProtocolVersion uint32 `json:"protocol_version"`
88 + ProtocolVersion string `json:"protocol_version"`
89 GeneratedAt time.Time `json:"generated_at"`
90 Self RelayDescriptor `json:"self"`
91 Relays []RelayDescriptor `json:"relays,omitempty"`
@@ -120,8 +120,8 @@ type UnregisterRequest struct {
120 }
121
122 type DomainResponse struct {
123 - SDKVersion string `json:"sdk_version"`
124 - ReleaseVersion string `json:"release_version"`
123 + ProtocolVersion string `json:"protocol_version"`
124 + ReleaseVersion string `json:"release_version"`
125 }
126
127 type TunnelStatusResponse struct {
types/discovery.go
-14
@@ -24,20 +24,6 @@ type RelayDescriptor struct {
24 SupportsTCP bool `json:"supports_tcp,omitempty"`
25 SupportsUDP bool `json:"supports_udp,omitempty"`
26 SupportsOverlayPeer bool `json:"supports_overlay_peer,omitempty"`
27 - SupportsWitness bool `json:"supports_witness,omitempty"`
28 - SupportsVPNExit bool `json:"supports_vpn_exit,omitempty"`
29 -
30 - StatusState string `json:"status_state,omitempty"`
31 -
32 - Region string `json:"region,omitempty"`
33 - Country string `json:"country,omitempty"`
34 -
35 - ReputationScore float64 `json:"reputation_score,omitempty"`
36 - WitnessCount uint64 `json:"witness_count,omitempty"`
37 - MITMSuspectedCount uint64 `json:"mitm_suspected_count,omitempty"`
38 - MITMQuarantined bool `json:"mitm_quarantined,omitempty"`
39 -
40 - LastMITMDetectedAt time.Time `json:"last_mitm_detected_at,omitempty"`
27
28 DescriptorSignature string `json:"descriptor_signature"`
29 }
types/types.go
+2 -2
@@ -2,10 +2,10 @@ package types
2
3 const (
4 ReleaseVersion = "v2.0.8"
5 - SDKProtocolVersion = "3"
5 + ProtocolVersion = "3"
6 PortalRelayRegistryURL = "https://raw.githubusercontent.com/gosuda/portal/main/registry.json"
7
8 - HeaderAccessToken = "X-Portal-Token"
8 + HeaderAccessToken = "X-Portal-Access-Token"
9 MarkerKeepalive = byte(0x00)
10 MarkerTLSStart = byte(0x02)
11 )
utils/crypto.go
-4
@@ -20,7 +20,6 @@ import (
20 )
21
22 type Secp256k1Identity struct {
23 - Generated bool `json:"generated,omitempty"`
23 Address string `json:"address"`
24 PublicKey string `json:"public_key"`
25 PrivateKey string `json:"private_key"`
@@ -135,14 +134,12 @@ func SignEthereumPersonalMessage(message, privateKeyHex string) (string, error)
134
135 func ResolveSecp256k1Identity(rawPrivateKey string) (Secp256k1Identity, error) {
136 privateKeyHex := strings.TrimSpace(rawPrivateKey)
138 - generated := false
137 if privateKeyHex == "" {
138 privateKey, err := secp256k1.GeneratePrivateKey()
139 if err != nil {
140 return Secp256k1Identity{}, fmt.Errorf("generate secp256k1 private key: %w", err)
141 }
142 privateKeyHex = hex.EncodeToString(privateKey.Serialize())
145 - generated = true
143 }
144
145 decoded, normalizedKeyHex, err := decodeSecp256k1PrivateKeyHex(privateKeyHex, true)
@@ -162,7 +159,6 @@ func ResolveSecp256k1Identity(rawPrivateKey string) (Secp256k1Identity, error) {
159 }
160
161 return Secp256k1Identity{
165 - Generated: generated,
162 Address: address,
163 PublicKey: publicKeyHex,
164 PrivateKey: normalizedKeyHex,