From aba0cc77c589fd81ad017ae2cd8f4e290d0acabd Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 13 Oct 2026 12:16:08 -0400 Subject: [PATCH 1/2] docs(moduleauthority): state WorkloadTokenFile's scope exactly; AGENTS no longer says nothing defaults N1: WorkloadTokenFile's godoc, ProjectedTokenFile's godoc, the README and the CHANGELOG said the helper names the token "wherever Codefly runs it". A native run sets CODEFLY__WORKLOAD_TOKEN_FILE only for a service that declares module-identity and not workload-identity (codefly-dev/cli cmd/run/native_cluster.go, NativePeer.TokenOnly). A workload-identity service receives its path as TOKEN_FILE in its workload-identity configuration, so for it the helper falls back to the pod mount, a path that does not exist under a native run; it passes ProjectedTokenFile(path) with the configured path. The docs now say so. N2: AGENTS.md said moduleauthority "takes both seams from the consumer rather than defaulting either". The seams still default no address (a zero Authority is a mint-only client), but since WorkloadTokenFile the package does resolve one value, the projected token's path, from the injected variable with the cell's mount as fallback. AGENTS.md now says which value that is and that the consumer opts into it. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 12 ++++++++++-- CHANGELOG.md | 6 +++++- README.md | 19 ++++++++++++------- moduleauthority/client.go | 23 +++++++++++++++++------ 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a4a44b8..15f14dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,8 +44,16 @@ Fleet standard — [handbook#68](https://github.com/obin-ai/handbook/issues/68). - **Never hardcode what the system resolves** — injected environment, derived ports, service addresses, credentials copied out of another component. This SDK is on the resolving side of that line: a facade takes a gateway and reads - `BaseURL()` off it rather than holding a URL, and `moduleauthority` takes - both seams from the consumer rather than defaulting either. Typing one of those in encodes something true + `BaseURL()` off it rather than holding a URL. `moduleauthority` takes its + gateway and authority seams from the consumer and defaults neither address: + a zero `Authority` builds a mint-only client that fails with + `ErrNoAuthority`, never one that dials an address nobody configured. The one + value it does resolve is a credential source the consumer opts into, the + projected token's path: `WorkloadTokenFile()` reads it from the variable a + native run injects (`CODEFLY__WORKLOAD_TOKEN_FILE`) and otherwise falls back + to the mount a cell's render declares, and a consumer whose path comes from + elsewhere passes `ProjectedTokenFile(path)`, which has no default. Typing one + of those in encodes something true on one machine for ten minutes, and it fails quietly — a runtime missing a credential can skip registration *silently*, so the service boots, serves, and is simply absent. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fff76a..0c0e96c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,11 @@ the mount minted nothing there. The variable and the path are exported as `WorkloadTokenFileVariable` and `PodWorkloadTokenFile`; codefly-dev/solution-runtime-go reads the same variable. It resolves the - path only: the token is still read before every mint. + path only: the token is still read before every mint. Its scope is the + services a native run names the variable for — `module-identity` without + `workload-identity`; a `workload-identity` service receives its path as + `TOKEN_FILE` in its `workload-identity` configuration and passes + `ProjectedTokenFile(path)` itself. ## v0.1.2 — core v0.19.1; Work Contexts are core's, carried opaque diff --git a/README.md b/README.md index 9d89ffc..7bc89e2 100644 --- a/README.md +++ b/README.md @@ -292,13 +292,18 @@ The SDK surfaces are: `AllowInsecureHTTP` asserts the hop is mesh-protected. SaaS owns the installed binding's audience, scopes, lifetime, verification, and audit record. - `WorkloadTokenFile()` is where Codefly projected the token for the process - it runs: the file `CODEFLY__WORKLOAD_TOKEN_FILE` names when that is set — - a native `codefly run` (CLI v0.1.187 and later) stands in for the cell and - projects each module's token to a file of its own, since a process cannot - read the pod mount — and the pod mount, `/var/run/secrets/codefly/token`, - otherwise. A consumer with another projection passes - `ProjectedTokenFile(path)` instead, which has no default. The host requires audience + `WorkloadTokenFile()` is where Codefly projected the token for a + module-identity service: the file `CODEFLY__WORKLOAD_TOKEN_FILE` names when + that is set, and the pod mount, `/var/run/secrets/codefly/token`, otherwise. + A native `codefly run` (CLI v0.1.187 and later) stands in for the cell and + projects each such service's token to a file of its own, since a process + cannot read the pod mount, but it sets that variable only for a service that + declares `module-identity` and does **not** declare `workload-identity`. A + `workload-identity` service gets its token's path as `TOKEN_FILE` in its + `workload-identity` configuration instead, so for it the helper falls back + to the pod path, which does not exist under a native run; it passes + `ProjectedTokenFile(path)` with that configured path. A consumer with any + other projection does the same; `ProjectedTokenFile` has no default. The host requires audience `accounts` and a token lifetime of at most 600 seconds, and verifies the pod, workload and approved build. The SDK rereads the source for every actual mint (including a module Work Context refresh), trims whitespace, and rejects an diff --git a/moduleauthority/client.go b/moduleauthority/client.go index e9042d1..8c1aa92 100644 --- a/moduleauthority/client.go +++ b/moduleauthority/client.go @@ -164,8 +164,9 @@ type ProjectedTokenSource interface { // one exists because moduleauthority does not import sdk-go/workcontext: it // carries Work Contexts as opaque strings and reads them only through core. // -// A module running under Codefly passes WorkloadTokenFile(), which names the -// path Codefly projected the token to wherever it runs. +// A module-identity service running under Codefly passes WorkloadTokenFile(), +// which names the path Codefly projected its token to in a cell's pod and in +// a native run; see WorkloadTokenFile for the one case it does not cover. type ProjectedTokenFile string // WorkloadTokenFileVariable is the environment variable that names the @@ -180,10 +181,20 @@ const WorkloadTokenFileVariable = "CODEFLY__WORKLOAD_TOKEN_FILE" // projected ServiceAccount token at, read-only, for audience `accounts`. const PodWorkloadTokenFile = "/var/run/secrets/codefly/token" -// WorkloadTokenFile is where this module's projected token is, wherever -// Codefly runs it: the file WorkloadTokenFileVariable names when the variable -// is set to a non-blank value, and PodWorkloadTokenFile otherwise. A process -// under a native run is told; a pod is not, and reads its mount. +// WorkloadTokenFile is where a module-identity service's projected token is: +// the file WorkloadTokenFileVariable names when the variable is set to a +// non-blank value, and PodWorkloadTokenFile otherwise. A process under a native +// run is told; a pod is not, and reads its mount. +// +// Its scope is exactly the services a native run names the variable for: a +// service that declares module-identity and does not declare +// workload-identity (codefly-dev/cli cmd/run/native_cluster.go). A service +// that declares workload-identity is not given the variable; a native run +// delivers its token's path as TOKEN_FILE in its workload-identity +// configuration instead, so for it this helper falls back to +// PodWorkloadTokenFile, a path that does not exist on the machine running it. +// Such a service passes ProjectedTokenFile with the path that configuration +// names. // // It resolves the path, never the token: the file is still read before every // mint, because the projection rotates under a running process. From 39cb6f89f93caacd4c44c64b083fb65d1e7be55e Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 13 Oct 2026 12:16:08 -0400 Subject: [PATCH 2/2] chore(scout): check the pipe's Close in the broker transport test golangci-lint (errcheck) flagged the unchecked net.Conn Close in brokerParserTransport. CI runs go vet, not golangci-lint, so it was never reported there. Co-Authored-By: Claude Opus 5.5 --- moduleauthority/broker_transport_errors_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moduleauthority/broker_transport_errors_test.go b/moduleauthority/broker_transport_errors_test.go index 68b3436..0923b4b 100644 --- a/moduleauthority/broker_transport_errors_test.go +++ b/moduleauthority/broker_transport_errors_test.go @@ -173,7 +173,7 @@ func brokerParserTransport(t *testing.T, wire string) *http.Transport { done := make(chan struct{}) go func() { defer close(done) - defer server.Close() + defer func() { _ = server.Close() }() req, err := http.ReadRequest(bufio.NewReader(server)) if err != nil { return