Skip to content

Let a remote loader read a flag the local layer carries - #44

Merged
andyyhope merged 1 commit into
mainfrom
feature/env-config-fetch-remote
Aug 28, 2026
Merged

Let a remote loader read a flag the local layer carries#44
andyyhope merged 1 commit into
mainfrom
feature/env-config-fetch-remote

Conversation

@andyyhope

Copy link
Copy Markdown
Owner

What

Adds EnvironmentConfiguration.fetchRemote(_:), so the remote layer can be fetched from a URL that itself lives in a flag the local config sets.

Why

Some apps store the remote config's endpoint in their bundled local config: the app only knows where to fetch from after the local layer is applied. load(_:) already applies the local layer in full before running the remote loader, so the timing was never the problem — the pole was. It doesn't exist when the configuration is built (the pole is constructed from config.sources), so a loader passed to init has nothing to resolve a flag against.

fetchRemote(_:) closes that construction-order knot: set the loader after the pole exists, capturing it.

let config = EnvironmentConfiguration(AppFlags.self, local: { env in bundledJSON(env) })
let pole = FlagPole(AppFlags.self, sources: [byHand] + config.sources)

config.fetchRemote { [pole] env in
    let url = pole.flags.checkout.remoteURL   // by-hand ▸ (remote cleared) ▸ local ▸ default
    return try await api.fetch(url)
}

Because the loader resolves through the whole pole, a by-hand override of the URL beats the bundled value — so a staging build can be pointed at a different config server from the companion without a rebuild.

API

  • New: EnvironmentConfiguration.fetchRemote(_:).
  • The remote: init parameter is now optional (defaults to a nil-returning loader). A nil loader leaves the remote layer .absent. Non-breaking — existing remote:-at-init call sites still compile.

Notes

  • The loader is snapshotted under the existing lock in load(_:), alongside epoch; remote moved from let to var.
  • Overlapping switches: a slow in-flight fetch may read the newer environment's URL, but the epoch guard drops its result as .superseded — a wasted fetch, never a wrong apply. Same guarantee as before.
  • Demo left unchanged: its remote config is genuinely fixed-per-environment, so fetchRemote there would be an artificial example.

Tests

Three added to EnvironmentConfigurationTests (TDD, red first):

  • loader runs after the local layer is applied,
  • loader resolves through a by-hand override sitting above local,
  • no loader → remote layer .absent.

Full suite: 774 pass, 0 failures. DocC builds clean under --warnings-as-errors (new subsection in SourcesAndPrecedence.md).

🤖 Generated with Claude Code

https://claude.ai/code/session_01NnwCqKNm8rZ63Au6bpYdaD

When the URL a remote config is fetched from is itself a flag — one the
bundled local config sets — the loader has to resolve it through the pole
after the local layer is applied. The pole does not exist when the
configuration is constructed (it is built from the configuration's own
sources), so a loader passed to init cannot reach it.

Add EnvironmentConfiguration.fetchRemote(_:) to set the loader once the
pole exists, capturing it. load(_:) already applies the local layer in
full before running the loader, so the flag resolves through the whole
stack — a by-hand override of the URL wins over the bundled value. The
init remote: parameter is now optional (a nil loader leaves the remote
layer absent), so existing call sites are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnwCqKNm8rZ63Au6bpYdaD
@andyyhope
andyyhope merged commit 3c8df9b into main Aug 28, 2026
6 checks passed
@andyyhope
andyyhope deleted the feature/env-config-fetch-remote branch August 28, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant