Domain Model
Entities, value objects, enums, and relationships in Courier's domain model.
This section defines all entities in the Courier system, their relationships, and the conventions used across the codebase. The domain model serves as the single source of truth for the data structures that underpin every subsystem.
4.1 Design Conventions
Aggregate roots are implemented as C# class types. These are mutable entities tracked by EF Core's change tracker and represent the top-level objects that own their child entities. Examples: Job, Connection, PgpKey, FileMonitor.
Value objects are implemented as C# record types. These are immutable, compared by value, and typically serialized as JSON columns or embedded within aggregate roots. Examples: StepConfiguration, FailurePolicy, TransferProgress, VerifyResult.
General conventions:
- All entities use
Guidprimary keys, generated application-side viaGuid.NewGuid() - All entities include
CreatedAtandUpdatedAttimestamps, set automatically via EF Core interceptors - All major entities support soft delete via an
IsDeletedflag andDeletedAttimestamp. Soft-deleted entities are excluded from normal queries via a global query filter - Nullable reference types are enabled project-wide. Non-nullable properties are required; nullable properties are optional
- Navigation properties use
IReadOnlyList<T>for collections to enforce modification through aggregate root methods - JSON columns (PostgreSQL
JSONB) are used for flexible, schema-light data like step configuration and audit details
4.2 Entity Catalog
4.2.1 Job System Entities
Job (aggregate root — class)
The central entity representing a named, versioned pipeline of steps.
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
Name | string | Human-readable job name |
Description | string? | Optional description |
CurrentVersion | int | Latest version number |
FailurePolicy | FailurePolicy | Value object: policy, max retries, backoff config |
IsEnabled | bool | Whether the job can be scheduled/triggered |
CreatedAt | DateTimeOffset | Creation timestamp |
UpdatedAt | DateTimeOffset | Last modification timestamp |
IsDeleted | bool | Soft delete flag |
DeletedAt | DateTimeOffset? | Soft delete timestamp |
Steps | IReadOnlyList<JobStep> | Ordered list of step definitions |
Schedules | IReadOnlyList<JobSchedule> | Attached schedules |
Versions | IReadOnlyList<JobVersion> | Historical configuration snapshots |
Executions | IReadOnlyList<JobExecution> | Execution history |
Dependencies | IReadOnlyList<JobDependency> | Upstream dependencies |
Tags | IReadOnlyList<EntityTag> | Associated tags |
JobStep (entity owned by Job)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
JobId | Guid | FK to parent Job |
StepOrder | int | Execution order (0-based) |
Name | string | Human-readable step name |
TypeKey | string | Step type identifier (e.g., sftp.download) |
Configuration | StepConfiguration | Value object serialized as JSONB |
TimeoutSeconds | int | Step timeout (default: 300) |
JobVersion (entity owned by Job)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
JobId | Guid | FK to parent Job |
VersionNumber | int | Incrementing version |
ConfigSnapshot | string | Full job configuration as JSON |
CreatedAt | DateTimeOffset | When this version was created |
CreatedBy | string | User who made the change |
JobExecution (aggregate root — class)
A single run of a job. Owns its step executions and context snapshot.
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
JobId | Guid | FK to Job |
JobVersionNumber | int | Which version was executed |
ChainExecutionId | Guid? | FK to ChainExecution if part of a chain |
TriggeredBy | string | "schedule", "manual:{userId}", "monitor:{monitorId}" |
State | JobExecutionState | Enum: Created, Queued, Running, Paused, Completed, Failed, Cancelled |
QueuedAt | DateTimeOffset? | When the execution entered the queue |
StartedAt | DateTimeOffset? | When execution began |
CompletedAt | DateTimeOffset? | When execution finished |
ContextSnapshot | string | Serialized JobContext as JSON for checkpoint/resume |
StepExecutions | IReadOnlyList<StepExecution> | Per-step execution records |
StepExecution (entity owned by JobExecution)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
JobExecutionId | Guid | FK to parent JobExecution |
JobStepId | Guid | FK to the JobStep definition |
StepOrder | int | Execution order |
State | StepExecutionState | Enum: Pending, Running, Completed, Failed, Skipped |
StartedAt | DateTimeOffset? | When step began |
CompletedAt | DateTimeOffset? | When step finished |
DurationMs | long? | Execution duration in milliseconds |
BytesProcessed | long? | Total bytes transferred/processed |
OutputData | string? | Step output written to JobContext, as JSON |
ErrorMessage | string? | Error message if failed |
ErrorStackTrace | string? | Stack trace if failed |
RetryAttempt | int | Current retry attempt number (0 = first try) |
JobChain (aggregate root — class)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
Name | string | Human-readable chain name |
Description | string? | Optional description |
IsEnabled | bool | Whether the chain can be scheduled/triggered |
CreatedAt | DateTimeOffset | Creation timestamp |
UpdatedAt | DateTimeOffset | Last modification timestamp |
IsDeleted | bool | Soft delete flag |
DeletedAt | DateTimeOffset? | Soft delete timestamp |
Members | IReadOnlyList<JobChainMember> | Ordered job references with dependencies |
Schedules | IReadOnlyList<ChainSchedule> | Attached schedules |
Tags | IReadOnlyList<EntityTag> | Associated tags |
JobChainMember (entity owned by JobChain)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
ChainId | Guid | FK to parent JobChain |
JobId | Guid | FK to the Job |
ExecutionOrder | int | Order within the chain |
DependsOnMemberId | Guid? | FK to upstream JobChainMember (null = chain entry point) |
RunOnUpstreamFailure | bool | Whether to run if upstream member fails (default: false) |
ChainExecution (aggregate root — class)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
ChainId | Guid | FK to JobChain |
TriggeredBy | string | "schedule", "manual:{userId}" |
State | ChainExecutionState | Enum: Pending, Running, Completed, Failed, Paused, Cancelled |
StartedAt | DateTimeOffset? | When chain execution began |
CompletedAt | DateTimeOffset? | When chain execution finished |
JobExecutions | IReadOnlyList<JobExecution> | All job executions within this chain run |
JobDependency (entity)
Represents a dependency edge between two standalone jobs (outside of chains).
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
UpstreamJobId | Guid | FK to the job that must complete first |
DownstreamJobId | Guid | FK to the job that depends on the upstream |
RunOnFailure | bool | Allow downstream to run even if upstream fails |
JobSchedule (entity)
Attached to a Job via job_schedules table.
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
JobId | Guid | FK to Job |
ScheduleType | string | "cron" or "one_shot" |
CronExpression | string? | Quartz cron expression (nullable for one-shot) |
RunAt | DateTimeOffset? | One-shot execution time (nullable for cron) |
IsEnabled | bool | Whether the schedule is active |
LastFiredAt | DateTimeOffset? | Last time this schedule triggered |
NextFireAt | DateTimeOffset? | Next calculated fire time |
ChainSchedule (entity)
Attached to a JobChain via chain_schedules table. Mirrors JobSchedule exactly.
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
ChainId | Guid | FK to JobChain |
ScheduleType | string | "cron" or "one_shot" |
CronExpression | string? | Quartz cron expression (nullable for one-shot) |
RunAt | DateTimeOffset? | One-shot execution time (nullable for cron) |
IsEnabled | bool | Whether the schedule is active |
LastFiredAt | DateTimeOffset? | Last time this schedule triggered |
NextFireAt | DateTimeOffset? | Next calculated fire time |
4.2.2 Connection Entities
Connection (aggregate root — class)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
Name | string | Human-readable name |
Group | string? | Organizational folder/group |
Protocol | ConnectionProtocol | Enum: SFTP, FTP, FTPS |
Host | string | Hostname or IP |
Port | int | Port number |
AuthMethod | AuthMethod | Enum: Password, SshKey, PasswordAndSshKey |
Username | string | Login username |
PasswordEncrypted | byte[]? | AES-256 encrypted password |
SshKeyId | Guid? | FK to SshKey |
HostKeyPolicy | HostKeyPolicy | Enum: TrustOnFirstUse, AlwaysTrust, Manual |
StoredHostFingerprint | string? | Known host key fingerprint |
PassiveMode | bool | FTP/FTPS: passive mode (default: true) |
TlsVersionFloor | TlsVersion? | FTPS: minimum TLS version |
TlsCertPolicy | TlsCertPolicy | FTPS: cert validation mode (SystemTrust, PinnedThumbprint, Insecure) |
TlsPinnedThumbprint | string? | FTPS: expected SHA-256 cert thumbprint (when policy is PinnedThumbprint) |
SshAlgorithms | SshAlgorithmConfig? | Value object serialized as JSONB |
ConnectTimeoutSec | int | Connection timeout (default: 30) |
OperationTimeoutSec | int | Per-operation timeout (default: 300) |
KeepaliveIntervalSec | int | Keepalive interval (default: 60) |
TransportRetries | int | Auto-reconnect attempts (default: 2) |
Status | ConnectionStatus | Enum: Active, Disabled |
FipsOverride | bool | Allow non-FIPS algorithms for this connection |
Notes | string? | Free-text notes |
CreatedAt | DateTimeOffset | |
UpdatedAt | DateTimeOffset | |
IsDeleted | bool | |
DeletedAt | DateTimeOffset? | |
Tags | IReadOnlyList<EntityTag> | Associated tags |
KnownHost (entity owned by Connection)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
ConnectionId | Guid | FK to Connection |
Fingerprint | string | SHA-256 fingerprint |
KeyType | string | Algorithm (e.g., ssh-rsa, ssh-ed25519) |
FirstSeen | DateTimeOffset | When first recorded |
LastSeen | DateTimeOffset | Last successful connection |
ApprovedBy | string | User or "system" for TOFU |
4.2.3 Key Store Entities
PgpKey (aggregate root — class)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
Name | string | Human-readable label |
Fingerprint | string | Full PGP fingerprint (40 hex chars) |
ShortKeyId | string | Short key ID (16 hex chars) |
Algorithm | PgpAlgorithm | Enum: RSA_2048, RSA_3072, RSA_4096, ECC_CURVE25519, etc. |
KeyType | PgpKeyType | Enum: PublicOnly, KeyPair |
Purpose | string? | Free-text notes |
Status | PgpKeyStatus | Enum: Active, Expiring, Retired, Revoked, Deleted |
PublicKeyData | string | ASCII-armored public key |
PrivateKeyData | byte[]? | AES-256 encrypted private key (null for public-only) |
PassphraseHash | string? | Encrypted passphrase |
ExpiresAt | DateTimeOffset? | Key expiration date |
SuccessorKeyId | Guid? | FK to replacement key (for rotation) |
CreatedBy | string | User who generated/imported |
CreatedAt | DateTimeOffset | |
UpdatedAt | DateTimeOffset | |
IsDeleted | bool | |
DeletedAt | DateTimeOffset? | |
Tags | IReadOnlyList<EntityTag> | Associated tags |
SshKey (aggregate root — class)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
Name | string | Human-readable label |
KeyType | SshKeyType | Enum: RSA_2048, RSA_4096, ED25519, ECDSA_256 |
PublicKeyData | string | OpenSSH-format public key |
PrivateKeyData | byte[] | AES-256 encrypted private key |
PassphraseHash | string? | Encrypted passphrase (nullable) |
Fingerprint | string | SHA-256 fingerprint |
Status | SshKeyStatus | Enum: Active, Retired, Deleted |
Notes | string? | Free-text notes |
CreatedBy | string | User who generated/imported |
CreatedAt | DateTimeOffset | |
UpdatedAt | DateTimeOffset | |
IsDeleted | bool | |
DeletedAt | DateTimeOffset? | |
Tags | IReadOnlyList<EntityTag> | Associated tags |
4.2.4 File Monitor Entities
FileMonitor (aggregate root — class)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
Name | string | Human-readable name |
Description | string? | Optional description |
WatchTarget | WatchTarget | Value object: target type (Local/Remote), path, connection ID |
TriggerEvents | TriggerEventFlags | Flags enum: FileCreated, FileModified, FileExists |
FilePatterns | List<string> | Glob patterns stored as JSONB array |
PollingIntervalSec | int | Polling interval (default: 60) |
StabilityWindowSec | int | File readiness window (default: 5) |
BatchMode | bool | True = batch, false = individual (default: true) |
MaxConsecutiveFailures | int | Error threshold (default: 5) |
ConsecutiveFailureCount | int | Current failure counter |
State | MonitorState | Enum: Active, Paused, Disabled, Error |
CreatedAt | DateTimeOffset | |
UpdatedAt | DateTimeOffset | |
IsDeleted | bool | |
DeletedAt | DateTimeOffset? | |
BoundJobs | IReadOnlyList<MonitorJobBinding> | Linked jobs/chains |
Tags | IReadOnlyList<EntityTag> | Associated tags |
MonitorJobBinding (entity owned by FileMonitor)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
MonitorId | Guid | FK to FileMonitor |
JobId | Guid? | FK to Job (nullable — set if bound to a job) |
ChainId | Guid? | FK to JobChain (nullable — set if bound to a chain) |
MonitorDirectoryState (entity owned by FileMonitor)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
MonitorId | Guid | FK to FileMonitor |
DirectoryListing | string | JSON array of {path, size, lastModified} |
CapturedAt | DateTimeOffset | When this snapshot was taken |
MonitorFileLog (entity owned by FileMonitor)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
MonitorId | Guid | FK to FileMonitor |
FilePath | string | Full path of detected file |
FileSize | long | Size in bytes at trigger time |
FileHash | string? | Optional SHA-256 hash |
LastModified | DateTimeOffset | File's last modified timestamp |
TriggeredAt | DateTimeOffset | When the trigger fired |
ExecutionId | Guid | FK to the JobExecution created |
4.2.5 Cross-Cutting Entities
Tag (aggregate root — class)
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
Name | string | Tag label (unique, case-insensitive) |
Color | string? | Hex color code for UI display (e.g., #FF5733) |
Category | string? | Grouping category (e.g., "Partner", "Environment") |
Description | string? | Optional description |
CreatedAt | DateTimeOffset | |
IsDeleted | bool | |
DeletedAt | DateTimeOffset? |
EntityTag (join entity)
Polymorphic association linking any taggable entity to a tag.
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
TagId | Guid | FK to Tag |
EntityType | TaggableEntityType | Enum: Job, JobChain, Connection, PgpKey, SshKey, FileMonitor |
EntityId | Guid | FK to the tagged entity (not a database FK — resolved in application) |
AuditLogEntry (entity — append-only)
Unified audit log with an entity type discriminator.
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
EntityType | AuditableEntityType | Enum: Job, JobExecution, StepExecution, Chain, Connection, PgpKey, SshKey, FileMonitor |
EntityId | Guid | FK to the audited entity |
Operation | string | Operation name (e.g., StateChanged, UsedForEncrypt, Connected) |
PerformedBy | string | User ID or "system" |
PerformedAt | DateTimeOffset | Timestamp |
Details | string | JSONB: operation-specific context (old/new state, error details, bytes transferred, etc.) |
The audit log uses a single table with a JSONB Details column for flexibility. Subsystem-specific fields (bytes transferred, transfer rate, error stack traces) are stored in the Details JSON rather than as top-level columns. This avoids a wide, sparse table while keeping all audit data queryable via PostgreSQL's JSONB operators.
Indexes: (EntityType, EntityId, PerformedAt) for entity-specific history queries, (PerformedAt) for time-range queries, (PerformedBy, PerformedAt) for user activity queries.
DomainEvent (entity — append-only)
Persisted domain events for V2 notification subscribers. Separate from the audit log because events are actionable (consumed by handlers) while audit entries are historical records.
| Property | Type | Description |
|---|---|---|
Id | Guid | Primary key |
EventType | string | Event name (e.g., JobCompleted, KeyExpiringSoon) |
EntityType | string | Source entity type |
EntityId | Guid | Source entity ID |
Payload | string | JSONB: event-specific data |
OccurredAt | DateTimeOffset | When the event occurred |
ProcessedAt | DateTimeOffset? | When a subscriber consumed the event (null = pending) |
ProcessedBy | string? | Which subscriber processed it |
In V1, events are written but ProcessedAt remains null (no subscribers yet). This table is designed as a transactional outbox: events are written in the same database transaction as the state change that produced them, guaranteeing consistency. In V2, this outbox becomes the foundation for event-driven scheduling — a relay process reads unprocessed events (using FOR UPDATE SKIP LOCKED to prevent duplicate delivery), publishes them to a message bus (Azure Service Bus or RabbitMQ), and marks them processed. This replaces database polling for job coordination and enables at-least-once delivery, fan-out notifications, and horizontal Worker scaling (see Section 15).
SystemSetting (entity)
Key-value configuration for runtime-adjustable settings.
| Property | Type | Description |
|---|---|---|
Key | string | Primary key (e.g., job.concurrency_limit) |
Value | string | Setting value (parsed by application) |
Description | string? | Human-readable description |
UpdatedAt | DateTimeOffset | Last modification timestamp |
UpdatedBy | string | User who changed the setting |
4.3 Value Objects
Value objects are implemented as C# record types. They are immutable, compared by value, and stored either as JSONB columns or as embedded properties within their parent entity.
public record FailurePolicy(
FailurePolicyType Type, // Stop, RetryStep, RetryJob, SkipAndContinue
int MaxRetries = 3,
int BackoffBaseSeconds = 1,
int BackoffMaxSeconds = 60);
public record StepConfiguration(
Dictionary<string, object> Parameters); // Flexible key-value config per step type
public record WatchTarget(
WatchTargetType Type, // Local, Remote
string Path, // Directory path
Guid? ConnectionId); // FK to Connection (null for local)
public record SshAlgorithmConfig(
List<string>? KeyExchange,
List<string>? Encryption,
List<string>? Mac,
List<string>? HostKey);
public record SplitArchiveConfig(
bool Enabled,
int MaxPartSizeMb = 500);
4.4 Enumerations
// Job System
public enum JobExecutionState { Created, Queued, Running, Paused, Completed, Failed, Cancelled }
public enum StepExecutionState { Pending, Running, Completed, Failed, Skipped }
public enum ChainExecutionState { Pending, Running, Completed, Failed, Paused, Cancelled }
public enum FailurePolicyType { Stop, RetryStep, RetryJob, SkipAndContinue }
public enum ScheduleType { Cron, OneShot }
// Connections
public enum ConnectionProtocol { SFTP, FTP, FTPS }
public enum AuthMethod { Password, SshKey, PasswordAndSshKey }
public enum HostKeyPolicy { TrustOnFirstUse, AlwaysTrust, Manual }
public enum TlsCertPolicy { SystemTrust, PinnedThumbprint, Insecure }
public enum ConnectionStatus { Active, Disabled }
public enum TlsVersion { TLS_1_0, TLS_1_1, TLS_1_2, TLS_1_3 }
// Keys
public enum PgpAlgorithm { RSA_2048, RSA_3072, RSA_4096, ECC_CURVE25519, ECC_P256, ECC_P384 }
public enum PgpKeyType { PublicOnly, KeyPair }
public enum PgpKeyStatus { Active, Expiring, Retired, Revoked, Deleted }
public enum SshKeyType { RSA_2048, RSA_4096, ED25519, ECDSA_256 }
public enum SshKeyStatus { Active, Retired, Deleted }
// File Monitor
public enum MonitorState { Active, Paused, Disabled, Error }
public enum WatchTargetType { Local, Remote }
[Flags]
public enum TriggerEventFlags
{
FileCreated = 1,
FileModified = 2,
FileExists = 4
}
// Cross-cutting
public enum TaggableEntityType { Job, JobChain, Connection, PgpKey, SshKey, FileMonitor }
public enum AuditableEntityType { Job, JobExecution, StepExecution, Chain, ChainExecution, Connection, PgpKey, SshKey, FileMonitor }
4.5 Entity Relationship Diagram
┌──────────────────────────────────────────────────────────────────────────────────┐
│ JOB SYSTEM │
│ │
│ ┌─────────┐ 1 * ┌──────────┐ ┌──────────────┐ │
│ │ Job │───────│ JobStep │ │ JobSchedule │ │
│ └────┬────┘ └──────────┘ └──────────────┘ │
│ │ 1 1 * │
│ ├──────────* ┌──────────────┐ │
│ │ │ JobVersion │ ┌────────────┐ 1 * ┌──────────────┐│
│ │ └──────────────┘ │ JobChain │─────│ChainSchedule ││
│ │ 1 └────┬────┘ └──────────────┘│
│ ├──────────* ┌──────────────┐ │ 1 │
│ │ │ JobExecution │◄──────────┐ ├──────* ┌────────────────┐ │
│ │ └──────┬───────┘ │ │ │ JobChainMember │ │
│ │ │ 1 │ │ └────────────────┘ │
│ │ ├────* ┌─────────────┤ │ 1 │
│ │ │ │StepExecution ││ ├──────* ┌────────────────┐ │
│ │ │ └──────────────┘│ │ │ChainExecution │ │
│ │ │ │ │ └────────────────┘ │
│ │ * └──────────────────────┘ │ │
│ ├────────────── ┌────────────────┐ │ │
│ │ │ JobDependency │ │ │
│ │ │ (upstream/ │ │ │
│ │ │ downstream) │ │ │
│ │ └────────────────┘ │ │
│ │ │ │
│ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │ CONNECTIONS │ │
│ │ │ │
│ │ ┌────────────┐ 1 * ┌───────────┐ │ │
│ │ │ Connection │───────│ KnownHost │ │ │
│ │ └──────┬─────┘ └───────────┘ │ │
│ │ │ │ │
│ │ │ *..1 │ │
│ │ ▼ │ │
│ │ ┌────────────┐ │ │
│ │ │ SshKey │ │ │
│ │ └────────────┘ │ │
│ │ │ │
│ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │ KEY STORE │ │
│ │ │ │
│ │ ┌────────────┐ │ │
│ │ │ PgpKey │ │ │
│ │ │ │───── successor_key_id │ │
│ │ └────────────┘ (self-ref) │ │
│ │ │ │
│ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │ FILE MONITORS │ │
│ │ │ │
│ │ ┌─────────────┐ 1 * ┌────────────────────┐ │ │
│ ├─│ FileMonitor │────│ MonitorJobBinding │─┘ │
│ │ └──────┬──────┘ └────────────────────┘ │
│ │ │ 1 │
│ │ ├──────* ┌───────────────────────┐ │
│ │ │ │ MonitorDirectoryState │ │
│ │ │ └───────────────────────┘ │
│ │ │ 1 │
│ │ └──────* ┌──────────────────┐ │
│ │ │ MonitorFileLog │ │
│ │ └──────────────────┘ │
│ │ │
│ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │ CROSS-CUTTING │
│ │ │
│ │ ┌─────────┐ * * ┌───────────┐ │
│ └─│ Tag │◄─────│ EntityTag │──── polymorphic FK to any taggable entity│
│ └─────────┘ └───────────┘ │
│ │
│ ┌────────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ AuditLogEntry │ │ DomainEvent │ │ SystemSetting │ │
│ │ (unified, │ │ (actionable, │ │ (key-value │ │
│ │ append-only) │ │ append-only) │ │ config) │ │
│ └────────────────┘ └──────────────┘ └───────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────┘
KEY RELATIONSHIPS:
Job ──1:*──► JobStep Job owns its ordered steps
Job ──1:*──► JobVersion Job owns its version history
Job ──1:*──► JobExecution Job owns its execution history
Job ──1:*──► JobSchedule Job can have multiple schedules
Job ──*:*──► Job Via JobDependency (upstream/downstream)
JobExecution ──1:*──► StepExecution Execution owns step executions
JobChain ──1:*──► JobChainMember Chain owns ordered member references
JobChain ──1:*──► ChainExecution Chain owns chain execution history
JobChain ──1:*──► ChainSchedule Chain can have multiple schedules
ChainExecution ──1:*──► JobExecution Chain execution owns job executions
Connection ──1:*──► KnownHost Connection owns host fingerprints
Connection ──*:1──► SshKey Connections reference SSH keys
FileMonitor ──1:*──► MonitorJobBinding Monitor binds to jobs/chains
FileMonitor ──1:*──► MonitorDirectoryState Monitor owns directory snapshots
FileMonitor ──1:*──► MonitorFileLog Monitor owns dedup log
MonitorJobBinding ──*:1──► Job/JobChain Binding references a job or chain
PgpKey ──0:1──► PgpKey Successor key (self-referencing)
Tag ──*:*──► (any taggable) Via EntityTag polymorphic join