Skip to content

endpoints: validate ingested endpoint carriers with core's ValidateEndpointCarriers #73

Open

Description

@antoinetoussaint-byte

Goal

Every endpoint carrier this SDK ingests is judged by core's resources.ValidateEndpointCarriers before it enters the snapshot that For(ctx) reads. After that, an absent carrier is a validated absence, never a mis-keyed carrier that reads as absent.

Found while moving to core v0.19.1 (#70, #71). It was left out of #71 on purpose because it changes endpoint resolution. The README now records the gap ("Core has shipped the declaration-aware carrier validator … since v0.14.0; this SDK does not call it yet").

What core offers (since v0.14.0)

resources.ValidateEndpointCarriers(envs []string, declared []*Endpoint) error (core#716) arrived in core v0.14.0 and is unchanged through v0.19.1, the version this SDK now pins. Every CODEFLY__ENDPOINT__* entry must be exactly the canonical key of one declared endpoint (EndpointAsEnvironmentVariableKey of its module, service, name and API), delivered once, with a non-empty value. Anything else is refused by name with ErrInvalidEndpointCarrier:

  • a carrier with the API of another declaration (the refusal names the declaration and its correct carrier);
  • a near-miss spelling;
  • a key that names no declared endpoint;
  • a duplicate;
  • an empty value.

The prefix is matched case-insensitively, so codefly__endpoint__… is refused as a non-canonical carrier rather than passed over. Every refusal is reported. Declarations that cannot spell a carrier, or two declarations that spell the same carrier, are refused with ErrInvalidEndpointDeclaration. The self carrier (SelfEndpointPrefix) and route carriers (RestRoutePrefix) are not judged.

Core's docs/architecture.md ("Exact comparison means a carrier whose identity is wrong…") states the contract on the SDK side: an SDK calls it at each path that ingests an environment, before publishing a snapshot, and a fallback consumes a validated absence.

Where sdk-go ingests carriers without calling it

These are as of main @ fb8c298 (v0.4.0). git grep ValidateEndpointCarriers finds nothing outside the README.

  • codefly.go:87 LoadEnvironmentVariables: builds the snapshot from os.Environ() plus file-carried values (fileCarriedValues). It filters with a case-sensitive strings.HasPrefix(env, "CODEFLY__"), so a lower-case codefly__endpoint__… is silently dropped rather than refused.
  • codefly.go:175 InjectEndpoints: checks only that module, service, name, API and address are non-empty, then publishes. Its keys are built by core, so they are canonical, but nothing checks them against what is declared to this consumer.
  • runtime_environment_file.go:24 LoadRuntimeEnvironmentFile: imports CODEFLY__* lines from an --output-env artifact into the process environment. Validation would happen on the next LoadEnvironmentVariables, not here.
  • codefly.go:202 rebuildEnvironmentSnapshotLocked: merges the three sources into the published snapshot.

The read side is for.go:247 carrierInstance. It calls resources.FindValueInEnvironmentVariables with the key built from the selected declaration (or, with no workspace, from the query's canonical identity at for.go:229) and treats any lookup error as absent.

Why adopting it changes endpoint resolution

  • Local fallback stops covering for a mis-keyed carrier. Take a process with a workspace, in local or with no environment set. Today a carrier spelled under another API, or with a near miss, sits under no key the lookup builds. ResolveNetworkInstance (for.go:166-174) then falls back to the native address with no error, so the process talks to a native address while the composition meant another. With validation it is an error.
  • Failure moves from query time to ingest time, and widens. Today one bad entry affects only the query that would have read it. Core's validator refuses the whole environment. So an ingest path (Init → LoadEnvironmentVariables, InjectEndpoints) can now fail on an entry no query ever reads. This includes a carrier for an endpoint the composition injected but that is not declared to this consumer.
  • Lower-case keys go from ignored to refused.
  • A deployed process has no declarations on disk. With no workspace, or a workspace that is not the process's composition (endpoints: a composed module run from its own checkout resolves from its injected carriers #66/fix(endpoints): the workspace on disk counts only if it is the composition the runtime names #68), there is nothing to pass as declared. Today the carriers there are authoritative and keyed by the query's canonical identity. Calling the validator with an empty declared would refuse every carrier and break every deployed process. Skipping it silently would leave the rule false exactly where carriers are authoritative. The fix has to choose, and the right answer may belong to the CLI, which renders the composition, rather than to this repo. If so, the deliverable here includes the issue against that owner.

What a fix must prove

Tests are in package codefly_test with t.Setenv and a LoadEnvironmentVariables reload after each mutation, so nothing asserts against a stale snapshot.

  1. Wrong API, workspace present, local: a carrier for a declared endpoint under another API is refused with ErrInvalidEndpointCarrier (via errors.Is) and no native fallback happens. The same test fails on main today: set it back and confirm it breaks.
  2. Near miss, lower case, duplicate, empty value: each is refused by name, at each ingest path (LoadEnvironmentVariables from the environment and from a file carrier, LoadRuntimeEnvironmentFile followed by Init, InjectEndpoints).
  3. A refused ingest publishes nothing: the previous snapshot stays in force, and no partial snapshot is visible to a concurrent For(ctx) (-race).
  4. A validated absence still falls back: with every carrier canonical and the queried one simply absent, local fallback works exactly as today.
  5. The workspace-free path is decided, not defaulted: whatever source of declared is chosen for a deployed process, the composition-root, deployed and foreign-workspace tests (endpoint_deployed_test.go, endpoint_composition_root_test.go, endpoint_foreign_workspace_test.go) pass unchanged. A test also pins the chosen behaviour for a carrier with no declaration.
  6. Docs: the README paragraph on carriers ("A carrier keyed differently from the selected declaration…") and the AGENTS.md rule on carriers describe the new behaviour in the same PR. The PR states which consumers could see a new boot-time refusal.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions