All notable changes to FastConf are documented here.
See docs/cookbook/migration-v0.18.md
for the step-by-step migration guide.
-
State.Lookupremoved. UseState.Explain(path)for the same provenance chain.LookupStrictis unchanged. -
Provider / generator failures have dedicated sentinels. Errors from
Provider.Loadnow satisfyerrors.Is(err, fastconf.ErrProvider); generator execution failures satisfyerrors.Is(err, fastconf.ErrGenerator). These failures no longer classify asErrDecode. -
pkg/flogmoved internal. FastConf's fluent logging wrapper is now an implementation detail underinternal/flog; applications should keep using their own logger and pass it withWithLogger. -
Public helper packages moved out of
pkg/. The old import paths remain as deprecated forwarding shims for the v0.20 migration window. Update new code to import the domain packages directly:Old import path New import path github.com/fastabc/fastconf/pkg/decodergithub.com/fastabc/fastconf/codecgithub.com/fastabc/fastconf/pkg/parsergithub.com/fastabc/fastconf/codecgithub.com/fastabc/fastconf/pkg/mergergithub.com/fastabc/fastconf/confmapgithub.com/fastabc/fastconf/pkg/mappathgithub.com/fastabc/fastconf/confmapgithub.com/fastabc/fastconf/pkg/typedgithub.com/fastabc/fastconf/confmapgithub.com/fastabc/fastconf/pkg/discoverygithub.com/fastabc/fastconf/overlaygithub.com/fastabc/fastconf/pkg/profilegithub.com/fastabc/fastconf/overlaygithub.com/fastabc/fastconf/pkg/transformgithub.com/fastabc/fastconf/transformgithub.com/fastabc/fastconf/pkg/migrationgithub.com/fastabc/fastconf/transformgithub.com/fastabc/fastconf/pkg/featuregithub.com/fastabc/fastconf/featuregithub.com/fastabc/fastconf/pkg/providergithub.com/fastabc/fastconf/providers/{env,cliflag,dotenv,labels}github.com/fastabc/fastconf/pkg/cliadaptergithub.com/fastabc/fastconf/providers/cliflaggithub.com/fastabc/fastconf/pkg/sourcegithub.com/fastabc/fastconf/providers/sourcegithub.com/fastabc/fastconf/pkg/validatecontracts.Schema+fastconf.NewValidator -
pkg/generatorremoved. Keep using the stablecontracts.Generatorinterface and returncontracts.RawLayervalues from your own generator implementations. -
WithProfile(ProfileOptions{Multi: ...})is last-write-wins. Multiple calls replace the active multi-profile set instead of appending. To activate a union, pass the full list in oneMultivalue. -
FastConf struct metadata now uses
fc:"...". The previous project-name tag key is removed with no compatibility fallback. Update field defaults, field metadata, and secret redaction markers:Addr string `json:"addr" fc:"default=:8080"` DSN string `json:"dsn" fc:"secret"` Port int `json:"port" fc:"required,min=1,max=65535"`
-
Subscribeis now diff-aware by default. The callback fires only when the value extracted byextractactually changes between two consecutive reloads. Equality is determined byreflect.DeepEqualon the dereferenced values. Two-nil transitions do not fire; nil ↔ non-nil transitions always fire.// v0.18: callback ran on every reload, you wrote the filter yourself fastconf.Subscribe(mgr, extract, func(old, new *T) { if old != nil && *old == *new { return } reconnect(new) }) // v0.19: framework owns the diff fastconf.Subscribe(mgr, extract, func(old, new *T) { reconnect(new) // guaranteed: value actually changed })
Quiet hazard. A v0.18 subscriber whose side effect ran on every reload (audit, mirror, heartbeat) without a caller-side filter will see fewer invocations after upgrading. To restore the v0.18 semantics, pass
WithEqual(func(_, _ *T) bool { return false }).
- Provider-owned maps are no longer mutated by the pipeline (fixed before first v0.20.0 tag). Provider snapshots are deep-cloned at the assembly boundary, so typed-hook / secret-resolve / merge writes can no longer reach a provider's (or the caller's) long-lived map — closing a secret-plaintext leak path.
Close()is now genuinely idempotent (fixed before first v0.20.0 tag). A secondClose()no longer panics on a double channel close, and the caller-side error-publish path is fenced against a send-on-closed race withClose.WithProviderOrderedno longer stripsSnapshotProvider. Ordered providers keep theirRevision/Stalemetadata (audit + Watch resume).- Patch-layer provenance now attributes only the paths a patch names,
not the entire merged tree, so
Explain/LookupStrictand audit attribution are correct. Plan().Run()runs on the single-writer goroutine, so a dry-run can no longer invoke user hooks concurrently with a reload; failing Plans are now published onErrors()as the godoc always claimed._meta.yamlread errors fail loud. A permission/IO error (as opposed to "not found") now fails the reload instead of silently degrading merge semantics.WithTransformers(nil)is rejected at construction instead of panicking on the reload goroutine.- Overlay/base directories that overflow their priority band fail the scan instead of silently interleaving layers across directories.
provenance.Origin.Valuematches its doc (Full-level only; slice leaves cloned). FeatureEvalno longer clones the whole rule table per call (0 allocs/op).
overlay.MetaSpecdrops the never-readOrderingandRedactEnvKeysfields. They were silently accepted and ignored; removed in the last pre-tag window to avoid carrying dead schema into SemVer.
WithEqual[M any](equal func(old, new *M) bool) SubscribeOption[M]— replaces the defaultreflect.DeepEqualcomparator with a custom function. Used to ignore noisy fields, hash-compare large structs, or restore the fire-on-every-reload idiom.
See docs/cookbook/migration-v0.19.md
for focused examples.
| v0.18 call | v0.19 replacement |
|---|---|
Subscribe(m, ex, fn) (callback expected every reload) |
Subscribe(m, ex, fn, WithEqual(func(_,_ *T) bool { return false })) |
Subscribe(m, ex, fn) with inline *old == *new filter |
Subscribe(m, ex, fn) — delete the filter |
First public release.
Three sub-module paths have changed. Update your go.mod and import statements:
| Old import path | New import path |
|---|---|
github.com/fastabc/fastconf/policy/cue |
github.com/fastabc/fastconf/cue/policy |
github.com/fastabc/fastconf/validate/cue/cuelang |
github.com/fastabc/fastconf/cue/cuelang |
github.com/fastabc/fastconf/providers/s3events |
github.com/fastabc/fastconf/providers/s3/s3events |
The cue/ top-level module (github.com/fastabc/fastconf/cue) replaces the two former
CUE sub-modules (policy/cue and validate/cue/cuelang), merging them under a single
shared cuelang.org/go runtime. providers/s3events is now a subpackage of providers/s3
(same go get github.com/fastabc/fastconf/providers/s3@latest install).
See docs/cookbook/migration-v0.18.md for full
examples and migration recipes.
- Bucketed options (SPEC-A1): 11 flat
With*setters replaced byWithProfile(ProfileOptions{…}),WithWatch(WatchOptions{…}), andWithCoalesce(CoalesceOptions{…}). The old names are deleted. WithDefaulterFunc→WithDefaults(SPEC-A6).Sub→Extract(SPEC-A8).State[T].Diffnow returns[]DiffEntry(SPEC-A4). Usefastconf.FormatDiff(entries)to get the previous[]stringline list.provider.NewCLIChangedremoved (SPEC-E2). Useprovider.NewCLI.OverlayAxis,Transformer,MigrationApplier,MigrationFunc,CodecBridgeare now root-native types (SPEC-A3). Field names are unchanged; existing struct literals compile without modification.
- Moved
Manager[T], the reload pipeline, plan/replay/watch helpers, and receiver-method internals behindinternal/manager. - Moved option storage into
internal/options, observability contracts intointernal/obs, tenant registry intointernal/tenant, and genericState[T]implementation intointernal/state. - Root package is now a 12-file public facade of type aliases,
constructors, and
With*wrappers. Public API signatures are unchanged. go.modminimum version lowered togo 1.22; all language features in use (generics,atomic.Pointer) are available since Go 1.18/1.19.
fastconf.Manager[T],State[T],Option,TenantManager[T],Replay[T],Watcher[T],PlanResult[T], and observability interfaces remain available at the root package through aliases.Manager.Getremains zero allocation; bench guard reports 0.4339 ns/op, 0 B/op, 0 allocs/op on the local arm64 baseline.
First numbered pre-public release. Tracks the contract-polish wave that
collapses every "shape-before-semantics" rough edge surfaced in
docs/plans/archive/2026-05-16-project-evaluation.md and lays the groundwork
for a 9+/10 publish-readiness score.
Reload(ctx)now threads the caller's ctx through the running pipeline. A timeout / cancellation actually aborts slowprovider.Load, secret resolvers, and transformers —ctx.Err()is returned raw (noErrDecodewrapping) soerrors.Is(err, context.DeadlineExceeded)works. fsnotify / provider-watcher triggers continue to usecontext.Background().- Failure-safe contract unchanged: a cancelled reload preserves the
previous
*State[T], does not advanceGeneration, and publishes the ctx error onErrors().
- Per-reporter bounded queue + dedicated worker goroutine replaces the
prior unbounded
go func()fan-out. Queue-full → drop +MetricsSink.EventDropped("diff-reporter:<idx>"). - New
WithDiffReporterQueueCap(n int)Option (default 64). - New optional
DiffReporterMetricsSinkextension interface — framework samples(depth, capacity)after every enqueue so a Prometheus gauge can show how close each reporter is to its drop threshold.
- New
*ProviderRegistrytype +NewProviderRegistry()constructor +WithProviderRegistry(r)Option. Lookup order: Manager-local → process-wide default. Lets multi-tenant tests and sub-systems isolate factories without mutating global state. WithProviderByNameresolution is now deferred to after every Option has applied, soWithProviderRegistrymay appear in any order relative to it. Existing zero-configRegisterProviderFactorycallers are unaffected.
State.MarshalYAML(redactor)honours the redactor — secret-marked fields are properly masked in the YAML output when a non-nil redactor is supplied. (Previously the parameter was reserved / ignored.)State.Redacted/Origins/Explain/Lookupnow tolerate a nil receiver the same wayDiff/MarshalYAML/Introspectalready did. Pinned byTestState_NilSafety.
providers/consul: no longer useshttp.DefaultClient(blocking queries up to 5 m are incompatible with a shared global client). Default client is an isolated&http.Client{}governed by ctx; configure transport-level limits viaWithClientwhen needed.providers/vault(AppRoleAuthfallback): same treatment — replaceshttp.DefaultClientwith an isolated&http.Client{Timeout: 10s}matching the main vault default. New cookbook page:docs/cookbook/provider-timeouts.md.
Everything below the v0.15.0 line is the same baseline as
[Unreleased] v0.14: strongly-typed *Manager[T], lock-free
atomic.Pointer.Load hot read, Kustomize-style overlay merge, opt-in
extension points, pre-public API (no migration commitments).
For full reference see README.md and docs/design/.