Repository navigation
fix(endpoints): the workspace on disk counts only if it is the composition the runtime names - #68
Merged
antoinetoussaint-byte merged 3 commits intoOct 12, 2026
Conversation
… not the process's composition (#66) A composed module run from its own checkout, or a released tag resolved into CODEFLY_MODULE_CACHE, finds the producer's own workspace.codefly.yaml above the process. It declares the module under the producer's name (saas-starter in saas-starter-dev), not the composition's (saas), so selection refused every endpoint query with "cannot find module <saas>", and services resolving their own endpoint at boot exited. ResolveNetworkInstance now trusts a found workspace only if it carries the queried module, by core's Workspace.Member (the lookup LoadModuleFromName resolves through). Otherwise the process is answered exactly as a workspace-free one: from the injected carrier, refusing absence with ErrEndpointCarrierAbsent; never the producer's declarations or native map. AGENTS.md was 205 lines on main and failed its 200-line budget test; the coverage-gate table it duplicated from the dual-module-change skill becomes a pointer to that skill. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ition the runtime names (#66) Review of #68 (F1-F6). Carrying the queried module is not the same as being this process's composition. robin, annotations, documents, model and runtime carry the same name in their producers' workspaces as in platform-core, so those were still trusted: selection against the producer's declarations, and in local a native address derived from the producer's workspace name. And a solution's tag, whose workspace imports its platform by release, failed to load ("requires host resolution") before any carrier was read. internal/enclosing now decides it once for every reader. With CODEFLY__WORKSPACE injected (core's SetIdentity sets it for every agent-run service), a found workspace counts only if the file declares that name, and one that does not is never loaded. With nothing injected, no composition started the process and the workspace found is used as found. A counted workspace must still carry the queried module; one that does not is answered from the carriers, and an absent carrier's refusal keeps the modules it does carry. The same rule now decides the Configuration/Secret local fallback, the fixture packages Principal resolves, and the name Workspace() reports, which is read from the file without composing it. Tests: same-name producer and solution-tag layouts under an injected composition; a composition root carrying modules through workspaces: and solutions:, its native address under its own name, and the modules a typo's refusal names; the configuration, fixture and Workspace() readers. The pre-existing test that dereferenced a nil instance now requires it, so a mutation reports every failure instead of one panic. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…jected name is matched exactly (#66) Verification of #68 (N1, N2). Under an injected composition, a workspace file that does not decode cannot name itself, so it still does not count and the injected carrier answers. But when no carrier was injected either, that file may have been the composition's own, so enclosing.Composition now returns it as Unreadable and the refusal (endpoint, configuration fallback, fixture lookup) carries its decode error instead of dropping it. With no composition injected, the decode error is returned as before. Tests pin the exact-match rule (a case-folded name does not count) and the undecodable file in all three cases: injected with a carrier, injected without one, and nothing injected. Docs: `codefly test` injects the identity too; only a plain `go test` or `go run` runs without it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
antoinetoussaint-byte
deleted the
issue-66-endpoints-a-composed-module-run-from-its-own
branch
October 12, 2026 20:13
This was referenced Oct 12, 2026
Closed
antoinetoussaint-byte
added a commit
to saurabhmaheshwari-obin/module-saas-starter
that referenced
this pull request
Oct 12, 2026
…inside a composition (codefly-dev#1095) sdk-go v0.3.1 resolved a service's own endpoint from the first workspace file above the process. A host run from its checkout or from a released tag in the module cache found its own development workspace, which names the module differently from the composition, so telemetry and accounts exited at start. v0.3.2 (codefly-dev/sdk-go#68) trusts a found workspace only when it is the process's injected composition. accounts' fixture seeding moves from codefly.Fixture() to fixture.Selected(), the API sdk-go#65 moved it to. Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #66. Supersedes #67 (see below).
Summary
ResolveNetworkInstancetook eligibility from whatever workspaceFindWorkspaceUpfound above the process. A composed module run from its own checkout, a released tag resolved intoCODEFLY_MODULE_CACHE, or a solution's tag finds its producer'sworkspace.codefly.yamlfirst. That file says nothing about the composition the process runs in:saas-starterinsaas-starter-dev, notsaas, so every query failed withcannot find module <saas>. Services that resolve their own endpoint at boot exited, which is why no released host above 0.0.93 boots.requires host resolution) before any carrier is read.The rule (
internal/enclosing, used by every reader that falls back to the workspace on disk):CODEFLY__WORKSPACEinjected: a found workspace counts only if its file declares that name. A workspace with another name is never loaded; only its name is decoded. Core'sEnvironmentVariableManager.SetIdentitysets this variable for every agent-run service, and the booted processes carriedCODEFLY__WORKSPACE=platform-core.go testorgo run;codefly runandcodefly testboth inject it), so the workspace found is the only one there is and is used as found. That is today's behaviour, held byTestAProcessNoCompositionStartedUsesTheWorkspaceAsFound. A plaincodefly runat a composition root injects the root's own name, so it matches.Workspace.Member. Modules composed throughworkspaces:orsolutions:count, and the native address is computed under the composition's name.ErrEndpointCarrierAbsent; a malformed one is refused as malformed. When a counted workspace does not carry the module (a typo at a composition root), the refusal still names the modules it does carry:does not compose module <x>, present: [...].The same rule now decides the other workspace readers:
Configuration/Secret;fixture.Selected().Principalresolves;codefly.Workspacereports when no identity is injected. That name is now read from the file without composing it, so a local tool under a solution's tag gets its name rather than a composition error.The container-context note in the issue is a different defect, and main already fixes it. v0.3.1 refused a workspace-free process in local or unset environments with
ErrNoDeclaredEndpointsbehind itsdeployed()gate. #62 removed that gate. Putting the gate back failsTestDeployedProcessResolvesItsCarrierWithoutAWorkspace,TestDeployedProcessRefusesAnAbsentCarrierWithTheSentinelandTestLocalProcessWithoutAWorkspaceRefusesAMalformedCarrier.Docs changed:
README.md: endpoint resolution, the composition rule, the other readers.AGENTS.md: the "deployed process has no workspace" rule, and thefixture/row, which pointed at a file that moved. AGENTS.md was 205 lines on main, failingTestAgentContextRootStaysWithinItsBudgetsince fix(workcontext)!: core's verifier is not re-exported; a verifier's keys come from an admitted endpoint, on the host's literal transport rule #53. It is now 200: the coverage-gate table duplicated thedual-module-changeskill's "Coverage, which is two different gates", so it became a pointer to the skill.scripts/allowed-imports.txtgainsroot github.com/codefly-dev/sdk-go/internal/enclosing. The deny-by-default gate refuses the new internal import without it. It is this module's own package and imports only coreresources.Why not #67. #67 makes the earlier one-line decision with a hand-rolled name loop. It also truncates
workcontext/AGENTS.mdfrom 422 lines to 7. #67 should be closed.Fix or hack
Fix, at the owner of endpoint resolution. Which composition a process runs in comes from what the runtime injects, and membership comes from core's own predicate; neither is a second implementation. The AGENTS.md and allowlist changes are documentation and gate bookkeeping, not behaviour.
Test plan
go test ./...and(cd workcontext && go test ./...)go test -race ./...and(cd workcontext && go test -race ./...)golangci-lint runin each module (0 issues)go mod tidyclean in each module;scripts/check-one-implementation.shokendpoint_foreign_workspace_test.gocovers four layouts:Each layout first asserts what trusting its workspace would do: refuse the module, carry it under the producer's name, or fail to load. The cases then run across environments local, unset and production, carriers from runtime and embedded sources, and every reference form. Every case resolves the carrier, refuses an absent one with the sentinel, refuses a malformed one as malformed, and never reads the producer's configuration files.
endpoint_composition_root_test.gocovers a root (platform-obin) that importsplatform-corethroughworkspaces:and addswikithroughsolutions:, with the composition injected and with nothing injected (F3, F4):Also:
TestFixturePrincipalReadsOnlyTheInjectedCompositionandTestWorkspaceNamesAWorkspaceOnlyTheCLICanCompose(F5). The pre-existingTestEndpointResolutionFallsBackToDeterministicLocalWorkspacenowrequires the instance before dereferencing it (F6). The shared test helpers clear any ambientCODEFLY__WORKSPACE.Mutations, each run against the whole root module and
fixture, with no panics:carriesrejects members composed from another workspacecarriesalways falseTestACompositionRootNamesWhatItCarriesWhenAModuleIsUnknownTestFixturePrincipalReadsOnlyTheInjectedCompositionWorkspace()composes the fileTestWorkspaceNamesAWorkspaceOnlyTheCLICanComposeTestAComposedModuleUnderItsOwnWorkspaceReadsNoProducerConfigurationTestACompositionRootCountsOnlyUnderTheExactInjectedNameTestAnUndecodableWorkspaceFileIsReportedNotTrustedTestAnUndecodableWorkspaceFileIsReportedNotTrustedLocal end-to-end proof (local override, no release)
The setup:
obin-ai/platform-coremainb095b29, which pinssaas0.0.94;codefly-dev/module-saas-starterv0.0.94 (9f6a4aa);codefly run service frontend --fixture dev-admin --headless --runtime-context native, each run under its own--naming-scope.The SDK comes from an uncommitted
code/go.work(use . <sdk-go checkout>) in each Go service; the go-grpc agent honours a source-ownedcode/go.work, not one at the repository root.accounts/code/fixtures/seed.gomoved tofixture.Selected()locally (see below).codefly.local.yamlresolve.saas.path: <v0.0.94 checkout>/modulede65fa9✓ Ready,GET /200; accounts JWKS 200…/module-cache/codefly-dev/module-saas-starter/v0.0.94/modulede65fa9a1885cc(previous head)1ce8b27telemetry: Codefly collector gRPC endpoint is unavailable: cannot find module <saas> in <saas-starter-dev>, present: [saas-starter], run exit 1 (the reviewer reproduced this too)On
de65fa9, telemetry and accounts ran with:CODEFLY__WORKSPACE=platform-coreandCODEFLY__MODULE=saas;workspace.codefly.yamlisname: saas-starter-dev;dep github.com/codefly-dev/sdk-go (devel).Telemetry logged
OpenTelemetry collector listening on Codefly gRPC endpoint …. Accounts loggedfixtures applied fixture=dev-admin, then its gRPC, REST, module-authority and Connect servers started.The host needs one change to build on this SDK, regardless of this PR. #65 moved the fixture API, so
module/services/accounts/code/fixtures/seed.go:185must changecodefly.Fixture()tofixture.Selected().Not verified
go mod downloadfails withchdir to cwd (…/services/telemetry/code) … no such file or directory. That belongs to the go-grpc agent / CLI container runner.CODEFLY_TEST_POSTGRES_DSN); CI runs them.🤖 Generated with Claude Code