diff --git a/ZB.MOM.WW.Secrets/Directory.Build.props b/ZB.MOM.WW.Secrets/Directory.Build.props
index c4755a6..3d9b855 100644
--- a/ZB.MOM.WW.Secrets/Directory.Build.props
+++ b/ZB.MOM.WW.Secrets/Directory.Build.props
@@ -7,6 +7,12 @@
latest
0.1.0
true
+ README.md
+
+
+
+
+
diff --git a/ZB.MOM.WW.Secrets/README.md b/ZB.MOM.WW.Secrets/README.md
new file mode 100644
index 0000000..4a7ec36
--- /dev/null
+++ b/ZB.MOM.WW.Secrets/README.md
@@ -0,0 +1,67 @@
+# ZB.MOM.WW.Secrets
+
+A reusable, envelope-encrypted **secrets manager** for the `ZB.MOM.WW.*` SCADA family:
+store secrets (SQL passwords, API tokens, connection strings) encrypted at rest and get the
+plaintext back on demand — from application code, from configuration, or from an operator UI.
+
+## Packages
+
+| Package | Purpose |
+|---|---|
+| `ZB.MOM.WW.Secrets.Abstractions` | Contracts only (`ISecretStore`, `ISecretResolver`, `IMasterKeyProvider`, `ISecretCipher`, `ISecretReplicator`, `ISecretCacheInvalidator`, `ISecretActorAccessor`), value types, exceptions. Dependency-light. |
+| `ZB.MOM.WW.Secrets` | Implementation: AES-256-GCM envelope cipher, env/file/DPAPI master-key providers, SQLite store, TTL resolver (audited), `${secret:}` config expander, `AddZbSecrets`. |
+| `ZB.MOM.WW.Secrets.Ui` | Blazor RCL on `ZB.MOM.WW.Theme`: list / add / rotate / delete + policy-gated, audited reveal. |
+
+A `secret` CLI (`set` / `get` / `list` / `rm` / `rotate`) ships in the repo (not packed).
+
+## How it protects secrets
+
+Each value is encrypted with a fresh **data key (DEK)** using AES-256-GCM; the DEK is then
+**wrapped** by a **master KEK** that never touches the database. The AAD binds the ciphertext
+to the secret's name and the wrapped DEK to the KEK id, so rows can't be swapped and a
+tampered/mis-keyed row **fails closed** (`SecretDecryptionException`) rather than returning a
+wrong value. The master KEK is resolved through a pluggable `IMasterKeyProvider`
+(environment variable, key file, or Windows DPAPI).
+
+## Wiring it up
+
+```csharp
+builder.Services.AddZbSecrets(builder.Configuration, "Secrets");
+
+// After config load, BEFORE options validation — expand ${secret:...} references:
+var expander = new SecretReferenceExpander(app.Services.GetRequiredService());
+await expander.ExpandConfigurationAsync((IConfigurationRoot)builder.Configuration, ct);
+```
+
+```jsonc
+// appsettings.json
+"Secrets": {
+ "SqlitePath": "secrets.db",
+ "MasterKey": { "Source": "Environment", "EnvVarName": "ZB_SECRETS_MASTER_KEY" },
+ "RunMigrationsOnStartup": true,
+ "ResolveCacheTtl": "00:00:30"
+},
+// Keep plaintext out of config — reference a stored secret instead:
+"ConnectionStrings": {
+ "Historian": "Server=...;User Id=sa;Password=${secret:sql/historian-password}"
+}
+```
+
+The master key is 32 bytes, provided base64 in `ZB_SECRETS_MASTER_KEY` (Environment source).
+A missing/invalid key fails closed at startup (`MasterKeyUnavailableException`).
+
+## Runtime + human access
+
+- **App code:** inject `ISecretResolver` and call `GetAsync(name, ct)`. Every resolve is
+ audited via `ZB.MOM.WW.Audit` (name + outcome only — the value is never logged).
+- **Operators:** mount the `ZB.MOM.WW.Secrets.Ui` `/admin/secrets` page. The list shows
+ **metadata only**; revealing a value requires the `secrets:reveal` authorization policy and
+ is audited. Add/rotate/delete require `secrets:manage`.
+
+## Clustered deployments
+
+Secrets storage is local SQLite by default. The schema already carries the
+`revision` / `updated_utc` / tombstone columns and an `ISecretReplicator` seam for
+cluster replication, but the Akka replicator (`ZB.MOM.WW.Secrets.Akka`) is a deferred
+follow-on. **When replicating across a node pair, every node must resolve the _same_ master
+KEK** (e.g. the same mounted key file) — a row wrapped by an unknown KEK fails closed.