Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,15 @@ see the skill below.
`SelectEndpointForReference` owns selection on module-adjusted declarations.
`Init` pins that consumer once; endpoint resolution refuses identity drift.
- **A deployed process has no workspace, so its carriers are authoritative.**
The builder image ships the binary alone. Outside `local` with no workspace
The builder image ships the binary alone, including in local Kubernetes pods.
In any environment with no workspace
on disk, `ResolveNetworkInstance` reads the carrier keyed by the query's
canonical identity and refuses only absence (`ErrEndpointCarrierAbsent`) or a
malformed value: the composition judged eligibility at render and the mesh
enforces it. With a workspace, selection comes first in every environment.
No flag decides the path; the environment and the workspace's presence do.
No flag decides the path; the workspace's
presence does. Native fallback requires a workspace and a `local` or unset
environment.
- **`CODEFLY__` is spelled in this repo, nowhere else.** A consumer needing a
value gets a typed accessor here; the prefix constant lives in
`runtime_value.go` for that reason.
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@ allocates an external instance.
With a workspace on disk, every query resolves its name and API through core's
typed selector on the producer's declarations before reading the injected
carrier, including queries with an explicit API. Ambiguous, forbidden, and
API-mismatched references are refused even if a matching carrier exists. A
local process without a workspace is refused with `ErrNoDeclaredEndpoints`: an
address carrier alone cannot establish eligibility there.
API-mismatched references are refused even if a matching carrier exists.

A deployed process has no workspace: the builder image ships the binary alone.
Outside `local`, with no workspace on disk, the carriers the composition
In every environment, with no workspace on disk, the carriers the composition
injected are the composition's judgement — the CLI's join decided at render
which endpoints the consumer may reach, and the mesh enforces it on the cell.
Such a process resolves from the carrier keyed by the query's canonical
identity (module, service, name, API; a name that is a supported API serves
that API, as core reads a declaration) and refuses only an absent carrier, with
`ErrEndpointCarrierAbsent`, or a malformed one. It never computes a native
address. A non-local process that does ship its workspace keeps selection
first. Nothing selects the path but the environment and the presence of a
workspace. A reference to an endpoint not named after its API must qualify the
address. This includes Kubernetes pods in a `local` environment: the environment
name does not mean the workspace is shipped in their image. A process that does
ship its workspace keeps selection first. The workspace's presence determines
the path; native fallback requires a workspace and a `local` or unset environment.
A reference to an endpoint not named after its API must qualify the
API with `.API(...)` there, since no declaration is on disk to supply it.

Call `codefly.Init(ctx)` at boot to pin the calling module from the runtime
Expand All @@ -62,8 +62,9 @@ queried across modules.
an address for an unavailable, external, forbidden, or invalid endpoint.
After selection, core's key/value lookup distinguishes an absent carrier from
a present value under the selected declaration's key. In unset or `local`
environments only absence permits native fallback; outside `local`, absence is
refused with `ErrEndpointCarrierAbsent`. A carrier keyed differently from the
environments with a workspace only absence permits native fallback; without
a workspace or outside `local`, absence is refused with
`ErrEndpointCarrierAbsent`. A carrier keyed differently from the
selected declaration — another API, or another spelling — is not found under
that key and reads as absent, so in `local` the native fallback runs in its
place until the declaration-aware carrier validator (core#716) lands with the
Expand Down
40 changes: 22 additions & 18 deletions endpoint_deployed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import (
// the composition's judgement — the CLI's join decided at render which
// endpoints this consumer may reach, and the mesh enforces it on the cell — so
// the SDK resolves from the carrier keyed by the query's canonical identity and
// refuses only an absent carrier or a malformed one. The environment and the
// presence of a workspace decide the path; no flag does.
// refuses only an absent carrier or a malformed one. This also describes pods
// deployed into a local environment: the workspace's presence, not its
// environment name, determines whether declarations can be checked on disk.

var carriedEndpoint = resources.EndpointInformation{
Module: "platform", Service: "location-records", Name: "rest", API: "rest",
Expand Down Expand Up @@ -65,7 +66,7 @@ var referenceForms = []struct {
}

func TestDeployedProcessResolvesItsCarrierWithoutAWorkspace(t *testing.T) {
for _, environment := range []string{"production", "staging"} {
for _, environment := range []string{"production", "staging", "local", ""} {
for _, source := range []string{"runtime", "embedded"} {
for _, form := range referenceForms {
t.Run("environment="+environment+"/source="+source+"/"+form.name, func(t *testing.T) {
Expand All @@ -87,7 +88,7 @@ func TestDeployedProcessResolvesItsCarrierWithoutAWorkspace(t *testing.T) {
}

func TestDeployedProcessRefusesAnAbsentCarrierWithTheSentinel(t *testing.T) {
for _, environment := range []string{"production", "staging"} {
for _, environment := range []string{"production", "staging", "local", ""} {
for _, form := range referenceForms {
t.Run("environment="+environment+"/"+form.name, func(t *testing.T) {
prepareProcessWithoutWorkspace(t, environment)
Expand Down Expand Up @@ -205,21 +206,24 @@ func TestNonlocalProcessWithAWorkspaceRefusesAbsenceWithTheSentinel(t *testing.T
require.Nil(t, instance)
}

// In local, or before an environment is selected, the declarations are the
// authority and a process without them is refused as #57 built it. A carrier
// alone establishes nothing there.
func TestLocalProcessWithoutAWorkspaceStillRequiresDeclarations(t *testing.T) {
// Local Kubernetes pods carry cluster service addresses but ship no workspace.
// A malformed injected address is refused; a missing workspace must not hide it
// behind a declaration error or permit a native address to be substituted.
func TestLocalProcessWithoutAWorkspaceRefusesAMalformedCarrier(t *testing.T) {
for _, environment := range []string{"", "local"} {
for _, form := range referenceForms {
t.Run("environment="+environment+"/"+form.name, func(t *testing.T) {
prepareProcessWithoutWorkspace(t, environment)
injectEndpointForTest(t, "embedded", carrierFor("rest", "rest", "https://upstream.example:9443"))
query := form.query(queryCarriedService())
instance, err := query.ResolveNetworkInstance()
require.ErrorIs(t, err, resources.ErrNoDeclaredEndpoints)
require.Nil(t, instance)
require.Nil(t, query.NetworkInstance())
})
for _, source := range []string{"runtime", "embedded"} {
for _, form := range referenceForms {
t.Run("environment="+environment+"/source="+source+"/"+form.name, func(t *testing.T) {
prepareProcessWithoutWorkspace(t, environment)
injectEndpointForTest(t, source, carrierFor("rest", "rest", "localhost:not-a-port"))
query := form.query(queryCarriedService())
instance, err := query.ResolveNetworkInstance()
require.ErrorContains(t, err, "malformed address")
require.NotErrorIs(t, err, codefly.ErrEndpointCarrierAbsent)
require.Nil(t, instance)
require.Nil(t, query.NetworkInstance())
})
}
}
}
}
28 changes: 9 additions & 19 deletions for.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ func (q *Query) NetworkInstance() *resources.NetworkInstance {
return nil
}

// ErrEndpointCarrierAbsent reports that a process outside the local
// environment has no carrier for the endpoint the query names. The composition
// injects a carrier for every endpoint it grants a consumer, so absence is the
// composition's answer and nothing is substituted for it: a deployed process
// ErrEndpointCarrierAbsent reports that a process has no carrier for the
// endpoint the query names and cannot use a local workspace fallback.
// The composition injects a carrier for every endpoint it grants a consumer,
// so absence is its answer and nothing is substituted for it: a deployed process
// has no workspace to compute a native address from, and a process that ships
// its workspace never falls back outside local.
var ErrEndpointCarrierAbsent = errors.New("codefly: no carrier for the endpoint the query names")
Expand All @@ -108,11 +108,12 @@ var ErrEndpointCarrierAbsent = errors.New("codefly: no carrier for the endpoint
// A deployed process has no workspace: the builder image ships the binary
// alone. There the carriers the composition injected are the composition's
// judgement — the CLI's join decided at render which endpoints this consumer
// may reach, and the mesh enforces it on the cell — so a non-local process
// may reach, and the mesh enforces it on the cell. A process in any environment
// with no workspace resolves from the carrier keyed by the query's canonical
// identity and refuses only an absent carrier (ErrEndpointCarrierAbsent) or a
// malformed one. It never computes a native address. No flag selects the
// path: the environment and the presence of a workspace decide.
// path: the presence of a workspace decides. Native fallback requires a
// workspace and a local or unset environment.
func (q *Query) ResolveNetworkInstance() (*resources.NetworkInstance, error) {
if err := checkConsumerIdentity(q.consumerModule); err != nil {
return nil, err
Expand All @@ -126,9 +127,6 @@ func (q *Query) ResolveNetworkInstance() (*resources.NetworkInstance, error) {
return nil, err
}
if workspace == nil {
if !deployed() {
return nil, resources.ErrNoDeclaredEndpoints
}
return q.resolveFromAuthoritativeCarrier()
}
// The declaration decides eligibility before any carrier can supply an
Expand All @@ -147,7 +145,7 @@ func (q *Query) ResolveNetworkInstance() (*resources.NetworkInstance, error) {
if present {
return instance, err
}
if deployed() {
if Environment() != "" && !IsLocal() {
return nil, absentCarrier(info)
}
local, localErr := q.localNetworkInstance(workspace, selected)
Expand All @@ -157,15 +155,7 @@ func (q *Query) ResolveNetworkInstance() (*resources.NetworkInstance, error) {
return local, nil
}

// deployed reports whether this process runs in an explicit, non-local
// environment: one the runtime composed and started, rather than one run from
// a workspace. Environment reads the runtime's carrier live, as every
// process-identity accessor does.
func deployed() bool {
return Environment() != "" && !IsLocal()
}

// resolveFromAuthoritativeCarrier answers a deployed process from its carriers
// resolveFromAuthoritativeCarrier answers a workspace-free process from its carriers
// alone. The carrier is keyed by the query's canonical identity — the
// declaration is not on disk to supply any part of it — and nothing is judged
// here: the composition already decided, at render, that this consumer may
Expand Down
Loading