agent endpoints: address an unnamed deployment as "_" (#4997)

An unnamed deployment was addressed as "default" and that spelling leaked
into scope keys, so embedders keyed endpoints differently from job dispatch.
Map "_" back to "" at the URL boundary, as the agent name already does,
and drop NormalizeDeployment.
This commit is contained in:
Paul Wells
2026-10-12 14:58:36 -07:00
committed by GitHub
parent 1d976b860a
commit eafd1056ca
4 changed files with 14 additions and 21 deletions
+5 -2
View File
@@ -415,10 +415,13 @@ func splitEndpointPath(u *url.URL) (endpointPath, error) {
return endpointPath{}, errMalformedPath
}
// "_" and "%5F" both land here; neither is a registrable name
// (IsReservedAgentName)
if agentName == UnnamedAgentSegment {
// (IsReservedAgentName, ValidateDeployment)
if agentName == UnnamedSegment {
agentName = ""
}
if deployment == UnnamedSegment {
deployment = ""
}
return endpointPath{agentName: agentName, deployment: deployment, path: path, escPath: escPath}, nil
}
+4
View File
@@ -287,6 +287,10 @@ func TestSplitEndpointPath(t *testing.T) {
{name: "bare underscore is unnamed", target: "/agents/_/d/x", agentName: "", deployment: "d", path: "/x", escPath: "/x"},
{name: "encoded underscore is unnamed", target: "/agents/%5F/d/x", agentName: "", deployment: "d", path: "/x", escPath: "/x"},
{name: "double encoded underscore is a name", target: "/agents/%255F/d/x", agentName: "%5F", deployment: "d", path: "/x", escPath: "/x"},
{name: "bare underscore deployment is unnamed", target: "/agents/a/_/x", agentName: "a", deployment: "", path: "/x", escPath: "/x"},
{name: "encoded underscore deployment is unnamed", target: "/agents/a/%5F/x", agentName: "a", deployment: "", path: "/x", escPath: "/x"},
{name: "unnamed agent and deployment", target: "/agents/_/_/x", agentName: "", deployment: "", path: "/x", escPath: "/x"},
{name: "default is a deployment name", target: "/agents/a/default/x", agentName: "a", deployment: "default", path: "/x", escPath: "/x"},
{name: "not an endpoint path", target: "/other/x", err: errNotEndpointPath},
{name: "no deployment segment", target: "/agents/a", err: errNotEndpointPath},
+4 -18
View File
@@ -21,29 +21,15 @@ import (
"sync/atomic"
)
// DefaultDeployment is the URL segment that addresses workers registered with an
// empty deployment name (self-hosted workers typically set none).
const DefaultDeployment = "default"
// UnnamedAgentSegment is the URL segment that addresses workers registered with
// an empty agent name.
const UnnamedAgentSegment = "_"
// UnnamedSegment is the URL segment that addresses workers registered with an
// empty agent name or deployment.
const UnnamedSegment = "_"
// IsReservedAgentName reports whether a name cannot address a worker. These are
// unreserved characters (RFC 3986 §2.3), so percent-encoding them yields no
// distinct form to address them by.
func IsReservedAgentName(agentName string) bool {
return agentName == UnnamedAgentSegment || agentName == "." || agentName == ".."
}
// NormalizeDeployment maps an empty deployment to the segment that addresses it
// in a URL. Every embedder must key its scopes through this, or a worker that
// registered without a deployment is unreachable from any node but its own.
func NormalizeDeployment(d string) string {
if d == "" {
return DefaultDeployment
}
return d
return agentName == UnnamedSegment || agentName == "." || agentName == ".."
}
// DefaultMaxStreams is the soft per-session concurrency cap used only for
+1 -1
View File
@@ -32,7 +32,7 @@ type endpointScopeKey struct {
}
func newEndpointScopeKey(apiKey, agentName, deployment string) endpointScopeKey {
return endpointScopeKey{apiKey, agentName, endpoint.NormalizeDeployment(deployment)}
return endpointScopeKey{apiKey, agentName, deployment}
}
// EndpointScopes maps that tenancy onto the endpoint package's scopes, one per