Why mamori

The primitives already exist. gocloud.dev/runtimevar watches a single variable. Viper and koanf do multi-source config. The AWS caching client and Vault’s LifetimeWatcher each refresh one backend. Nobody composed them into typed, validated, watchable config with a provider ecosystem, so every production service ends up hand-rolling a ConfigManager with a ticker, a mutex, and a prayer.

mamori is that glue, done once. It is the External Secrets Operator provider model one layer down: a library inside your process, not an operator inside your cluster.

Compared to the alternatives

Typed struct + tagsMulti-sourceSecrets first-classRuntime watchDiff-aware callbackProvider ecosystem
mamoriyesyesyesnative + pollyesyes, with a conformance kit
runtimevarnoone var at a timeweakyesnodriver matrix
Viper / koanfyesyesbolted onafterthoughtnoconfig-first
AWS SM cache / Vault LifetimeWatchernosingle backendnativenative, per backendnosiloed
envconfig / caarlos0/envyesenv onlynoload-oncenono
The operational layer is a second axis the alternatives mostly leave to you: knowing whether config is healthy, expressing precedence between sources, and classifying why a resolve failed.
Precedence chainsHealth introspectionError classificationPre-deploy checkStarts during an outageFan-out server
mamoriper-field, ordered, with onfailStatus / Health / HTTP admin endpointerrors.Is to a typed kind, conformance-enforcedDoctor (library) and mamori doctor (live)opt-in encrypted snapshotoptional mamori:// config server
runtimevarnoper-variable value onlydriver-specificnonono
Viper / koanfkey override order, no per-key policynonononono
AWS SM cache / Vault LifetimeWatchernoper backendbackend-specificnonono
envconfig / caarlos0/envnononononono
  • Precedence chains: a source tag can list several refs (env:X,aws-sm://x); the first that resolves wins, and onfail (keeplast / useDefault / fail) governs what happens when the winner later errors. Viper and koanf resolve a merged key space rather than an ordered per-field chain, and neither carries a failure policy.
  • Health introspection and error classification: every resolve error maps to a typed Kind (permission_denied, unavailable, …) that survives errors.Is, and Status/Health expose it live. mamori.Doctor runs the same wiring in CI before a deploy; mamori doctor queries a running process’s admin endpoint. The alternatives surface a raw backend error, if any, and leave “is my config healthy” to you.

Where mamori fits

  • gocloud.dev/runtimevar is the closest primitive. mamori adds struct composition, tags, validation, diff callbacks, and secret hygiene. A runtimevar bridge provider could even inherit its driver matrix.
  • External Secrets Operator solves the same provider problem at the cluster layer by materializing Kubernetes Secrets. mamori is complementary: it is for apps that want to skip the Kubernetes Secret hop, or that do not run on Kubernetes at all. It keeps no persistent external state, so there is no finalizer lifecycle to manage.
  • Viper / koanf are config-first with secrets bolted on. mamori is secrets-first with config included. Already running Viper? The viper:// provider gives you an incremental, field-by-field path that keeps Viper doing exactly what it does today.
  • spring-cloud-config and .NET IOptionsMonitor<T> are the developer-experience benchmark from other ecosystems. Watch().Get() is mamori’s IOptionsMonitor<T>.CurrentValue.

What mamori is not

  • Not a secrets store: your backend remains the source of truth, and mamori keeps no state you have to reconcile or back up. The optional config server is a read-through fan-out in front of your existing backends, not a place secrets live. The opt-in bootstrap cache does write an encrypted snapshot to disk, but only so a restart survives an outage; it is a cache of what your backend already holds, never an authority, and it is off by default.
  • Not a sync engine between stores (that is ESO / vals / teller territory).
  • Not a general feature-flag system, though a flags provider could be built on top.
  • Not cross-language: it is deliberately Go-idiomatic.