1
2
3
4
5
6
7
8
9
10
Application (1)
│
├── AppEnvironment (N) ← named scopes: "local", "staging", "production"
│
├── Secret (N)
│ └── SecretVersion (N) ← one per (Secret × AppEnvironment × write)
│ └── EnvironmentId → AppEnvironment
│
└── ConfigurationEntry (N)
└── EnvironmentId → AppEnvironment
All child entities reference Application via ApplicationId. Global query filters on Application propagate soft-delete automatically: when an application is soft-deleted, its secrets and configs become invisible to all queries.
The top-level owner of all other entities. Represents a single service or workload.
| Column | Type | Notes |
|---|---|---|
Id |
Guid |
Primary key |
Name |
string |
Display name (up to 200 chars) |
Slug |
string |
URL-safe, unique identifier generated from Name |
Description |
string? |
Optional description |
ApiKeyHash |
string |
PBKDF2 hash of the plain API key |
IsDeleted |
bool |
Soft-delete flag |
CreatedAt |
DateTimeOffset |
— |
UpdatedAt |
DateTimeOffset |
— |
A named deployment environment (e.g. local, staging, production) scoped to an application.
| Column | Type | Notes |
|---|---|---|
Id |
Guid |
Primary key |
ApplicationId |
Guid |
FK → Application |
Name |
string |
Display name |
Slug |
string |
URL-safe identifier |
CreatedAt |
DateTimeOffset |
— |
UpdatedAt |
DateTimeOffset |
— |
A local environment is automatically seeded when an application is created.
A named secret slot. Does not hold a value itself — values live in SecretVersion.
| Column | Type | Notes |
|---|---|---|
Id |
Guid |
Primary key |
ApplicationId |
Guid |
FK → Application |
Name |
string |
Identifier used in API calls and client reads |
Description |
string? |
Optional |
CreatedAt |
DateTimeOffset |
— |
UpdatedAt |
DateTimeOffset |
— |
An immutable encrypted value for a (Secret, AppEnvironment) pair. Every write creates a new version; old versions are never overwritten.
| Column | Type | Notes |
|---|---|---|
Id |
Guid |
Primary key |
SecretId |
Guid |
FK → Secret |
EnvironmentId |
Guid |
FK → AppEnvironment |
EncryptedValue |
string |
Base64-encoded nonce(12) + tag(16) + ciphertext |
IsEnabled |
bool |
Disabled versions are skipped on consumer read |
ExpiresAt |
DateTimeOffset? |
Version is inactive after this timestamp |
NotBefore |
DateTimeOffset? |
Version is inactive before this timestamp |
Version |
int |
Monotonically increasing per (Secret, Environment) |
CreatedAt |
DateTimeOffset |
— |
The consumer API returns the highest-version record where:
IsEnabled = trueNotBefore IS NULL OR NotBefore <= nowExpiresAt IS NULL OR ExpiresAt >= nowA plain-text key-value pair for feature flags and non-secret settings.
| Column | Type | Notes |
|---|---|---|
Id |
Guid |
Primary key |
ApplicationId |
Guid |
FK → Application |
EnvironmentId |
Guid |
FK → AppEnvironment |
Key |
string |
Configuration key (e.g. Feature:DarkMode, MaxRetries) |
Value |
string |
Plain text — not encrypted |
Description |
string? |
Optional |
CreatedAt |
DateTimeOffset |
— |
UpdatedAt |
DateTimeOffset |
— |
Configuration entries are stored unencrypted. Do not store sensitive values as configuration entries — use secrets instead.
1
2
// ApplicationConfiguration.cs
builder.HasQueryFilter(a => !a.IsDeleted);
Because Application has a global filter, EF Core automatically appends WHERE IsDeleted = 0 to every query involving Application. All child entities that navigate back to Application are similarly filtered.
| Entity | Unique on |
|---|---|
Application |
Slug |
AppEnvironment |
(ApplicationId, Slug) |
Secret |
(ApplicationId, Name) |
ConfigurationEntry |
(ApplicationId, EnvironmentId, Key) |
These are enforced at the database level and surface as 409 Conflict responses in the API.
Application names are converted to slugs using SlugService.Generate():
1
2
3
"My New App" → "my-new-app"
"Hello! World" → "hello-world"
"A---B---C" → "a-b-c"