Priority
P1 — High — make runtime policy fallback semantics predictable before production use of partial configuration overrides.
Current problem
cacheConfigProvider currently has whole-object replacement semantics:
- A provider result of
null falls back to the cached definition's defaultConfig.
- Any non-null provider result replaces
defaultConfig completely.
- Missing per-layer TTL or ramp values then disable that layer with
reason="missing_config".
DialCacheKeyConfig normalizes an omitted requestLocal value to false, so runtime omission cannot be distinguished from an explicit disable.
That makes an empty or partial runtime result an implicit kill switch and contradicts the intended rule that omitted runtime values inherit documented defaults.
Current evidence:
Confirmed contract
Baseline defaults
defaultConfig remains optional.
- When
defaultConfig is absent, the use case's baseline is fully disabled.
requestLocal defaults to false.
- A local or remote layer with no effective TTL is disabled by policy.
- A layer with an effective TTL but no ramp defaults to ramp
100.
- There is no universal TTL. A use case or runtime override must provide a TTL to enable a shared layer.
Runtime overlay
- Resolve the provider once per enabled invocation.
- A provider result of
null, defensive JavaScript undefined, or an empty config applies no overrides.
- A non-null runtime config overlays
defaultConfig field by field.
- Missing or
undefined runtime requestLocal, TTL, or ramp values inherit the corresponding baseline value.
requestLocal: false explicitly disables request-local caching.
ramp[layer]: 0 explicitly disables that local or remote layer.
- Explicit malformed values do not fall back to defaults. They retain fail-open validation behavior.
- A provider exception is not absence: keep the current uncached
config_error path so a control-plane failure cannot accidentally re-enable caching.
Metrics
- Remove
missing_config as a normal disabled reason.
- Use a bounded
policy_disabled reason when the fully resolved baseline plus overlay leaves a shared layer disabled.
- Preserve
invalid_ttl, ramped_down, config_error, and context.
- Do not emit per-call request-local-disabled noise for the normal
requestLocal: false default.
Minimal implementation scope
- Keep the current public TTL/ramp map shape; do not introduce a new policy object or builder hierarchy.
- Preserve omission in
DialCacheKeyConfig.requestLocal until the baseline and runtime overlay are resolved.
- Add one internal merge/resolution path used by request-local, process-local, and Redis layers.
- Validate static/default policy when
cached() registers the use case where validation is possible.
- Keep dynamic malformed values fail open at invocation time.
- Update Prometheus, Datadog, custom-adapter types, README examples, migration notes, and packed declaration/runtime coverage.
- Do not change cache keys, Redis frames, Lua scripts, invalidation identity, serializers, or client ownership.
Compatibility and migration
This is a behavior and metrics-label compatibility change:
- Partial runtime configs begin inheriting unspecified defaults instead of disabling those layers.
- An empty runtime config becomes a no-op rather than disable-all.
- Runtime omission of
requestLocal inherits the baseline instead of forcing false.
- A configured layer with omitted ramp becomes fully enabled at ramp
100.
- Existing runtime kill switches that rely on omission must use explicit
ramp: 0 and requestLocal: false.
- Dashboards using
reason="missing_config" migrate to reason="policy_disabled".
- Definitions with no
defaultConfig remain valid and default to all caching disabled until a runtime overlay explicitly enables a layer.
Acceptance criteria
- Cover null, undefined, empty, partial, complete, and throwing provider results.
- Cover leaf-level inheritance and overrides independently for request-local, local TTL/ramp, and remote TTL/ramp.
- Prove explicit
false and ramp 0 disable inherited enabled policies.
- Prove an omitted default config is a fully disabled baseline rather than missing configuration.
- Prove explicit invalid TTL/ramp values are not masked by defaults.
- Preserve one provider resolution per invocation.
- Cover local-only, remote-only, layered, request-local-only, asymmetric TTL, partial rollout, and disable-all examples.
- Cover the complete backend-neutral/Prometheus/Datadog disabled-reason set.
- Add a prominent migration example from omission-as-disable to explicit disable.
- Document the precedence order and every default in one authoritative README section.
Priority
P1 — High — make runtime policy fallback semantics predictable before production use of partial configuration overrides.
Current problem
cacheConfigProvidercurrently has whole-object replacement semantics:nullfalls back to the cached definition'sdefaultConfig.defaultConfigcompletely.reason="missing_config".DialCacheKeyConfignormalizes an omittedrequestLocalvalue tofalse, so runtime omission cannot be distinguished from an explicit disable.That makes an empty or partial runtime result an implicit kill switch and contradicts the intended rule that omitted runtime values inherit documented defaults.
Current evidence:
Confirmed contract
Baseline defaults
defaultConfigremains optional.defaultConfigis absent, the use case's baseline is fully disabled.requestLocaldefaults tofalse.100.Runtime overlay
null, defensive JavaScriptundefined, or an empty config applies no overrides.defaultConfigfield by field.undefinedruntimerequestLocal, TTL, or ramp values inherit the corresponding baseline value.requestLocal: falseexplicitly disables request-local caching.ramp[layer]: 0explicitly disables that local or remote layer.config_errorpath so a control-plane failure cannot accidentally re-enable caching.Metrics
missing_configas a normal disabled reason.policy_disabledreason when the fully resolved baseline plus overlay leaves a shared layer disabled.invalid_ttl,ramped_down,config_error, andcontext.requestLocal: falsedefault.Minimal implementation scope
DialCacheKeyConfig.requestLocaluntil the baseline and runtime overlay are resolved.cached()registers the use case where validation is possible.Compatibility and migration
This is a behavior and metrics-label compatibility change:
requestLocalinherits the baseline instead of forcingfalse.100.ramp: 0andrequestLocal: false.reason="missing_config"migrate toreason="policy_disabled".defaultConfigremain valid and default to all caching disabled until a runtime overlay explicitly enables a layer.Acceptance criteria
falseand ramp0disable inherited enabled policies.