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,