DEV Community

Ivan Rossouw
Ivan Rossouw

Posted on

Configuration Is Not Authority: A Better Boundary for Runtime Policy

A runtime setting can play two very different roles.

At the edge of an application, it may be a useful preference: which environment should this UI request, which options should it display, or which workflow should it prepare? At an authoritative backend boundary, the same value may decide where work is validated and executed.

Those roles look similar in a request model. Architecturally, they are not.

I was reminded of this while reviewing a committed .NET change that crossed several front ends and backend paths. A mode had previously been fixed to one value. That meant an alternative environment could be correctly configured yet remain invisible in multiple screens. Threading a configurable value through the pages solved the visibility problem, but it raised a more important question:

Who is allowed to decide the effective mode?

A request value is not a policy

The UI needs enough information to ask for the right options. It may therefore send a requested mode with a query or command. That is useful context, but a payload is controlled by the caller. It cannot become authoritative merely because it uses the same enum as the server.

The safer distinction is:

  • Request projection: helps a front end render and submit a coherent flow.
  • Effective policy: comes from trusted server-side state and controls validation and execution.

When the two disagree, the server policy wins.

This matters beyond security. If discovery uses one mode, revalidation uses another, and execution starts in a third, the workflow becomes internally inconsistent. A user can select an option that the server later rejects—or, worse, the server can start work under assumptions that were never presented to the user.

Put the seam in the reusable module

A reusable module should not import a host application’s configuration store, database model, or settings service. That reverses the dependency direction and makes the module aware of infrastructure it does not own.

Instead, place a narrow policy contract beside the module’s domain-facing abstractions:

public interface IRuntimePolicyResolver
{
    ValueTask<ExecutionMode> ResolveAsync(
        Guid scopeId,
        CancellationToken cancellationToken);
}
Enter fullscreen mode Exit fullscreen mode

The host implements that interface using its own trusted settings. The module asks for policy without knowing where it is stored.

The client-side need can have a separate, deliberately weaker abstraction:

public interface IRequestModeProvider
{
    ExecutionMode Mode { get; }
}
Enter fullscreen mode Exit fullscreen mode

Its implementation might read deployment configuration so several pages request the same environment. Its purpose is consistency at the edge, not authority in the backend.

Two abstractions for one enum can initially feel like duplication. In fact, the names make the trust boundary visible. One answers, “What should this front end request?” The other answers, “What will the server allow?”

Resolve once, then carry the effective value

The authoritative value must cover the whole operation.

In the change I reviewed, the server-side policy was applied at the important chokepoints: option discovery, selection revalidation, and the shared execution-start path. The same resolved value flowed into both validation and execution.

That is the key. Resolving policy only when options are listed leaves a time-of-check/time-of-use gap. Resolving it only at execution creates a confusing experience because the screen may present choices that can never start.

A generalized orchestration shape looks like this:

var effectiveMode =
    await policy.ResolveAsync(context.ScopeId, cancellationToken);

var option = await options.RevalidateAsync(
    context.Selection,
    effectiveMode,
    cancellationToken);

await executor.StartAsync(
    option,
    effectiveMode,
    cancellationToken);
Enter fullscreen mode Exit fullscreen mode

The caller may still carry its requested value for correlation or diagnostics, but it does not decide the execution boundary.

Test disagreement, not only configuration parsing

Parsing tests are useful: explicit values should work, and missing, blank, or unknown values should take a deliberate safe default. But they are not enough.

The higher-value tests exercise authority:

  1. The front ends consistently forward their configured request mode.
  2. The server receives one mode while trusted policy resolves another.
  3. Revalidation uses the trusted value.
  4. Execution uses that same trusted value.
  5. A missing host registration has explicitly documented compatibility behaviour.
  6. Any identifier translation between runtime context and persisted settings is pinned by a focused test.

That last point is easy to underestimate. A harmless-looking identifier format mismatch can make a valid setting appear absent and silently trigger the fallback.

The reviewed changes include focused unit and component coverage around these boundaries. They do not prove production behaviour, and I would still want an end-to-end test that deliberately creates a caller/server disagreement across the complete round trip.

The trade-off is explicit complexity

This design adds a seam, host wiring, fallback semantics, and more tests. It also creates two values that must be named carefully because they have intentionally different authority.

A safe default can introduce its own operational symptom. If the trusted policy falls back to the primary environment while only the alternate environment is configured, the UI may show no available options. That is safer than silently executing in the wrong place, but it needs clear diagnostics.

Optional dependency injection is another trade-off. It helps existing hosts adopt the new contract without a breaking change, but it can also preserve weaker legacy behaviour when a host forgets to register the resolver. Compatibility should be temporary and observable, not accidental.

A practical rule

For cross-cutting runtime choices:

  • configure the request at the edge;
  • resolve effective policy at the trusted boundary;
  • keep the contract in the reusable module and the implementation in the host;
  • carry one effective value through discovery, validation, and execution;
  • test caller/server disagreement as a first-class case.

Configuration tells a component what someone would like to happen. Authority decides what is allowed to happen. Keeping those concepts separate costs a little more wiring, but it makes both the module boundary and the trust boundary much easier to reason about.

Top comments (0)