using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace w4c_workflows.Models; // --------------------------------------------------------------------------- // Well-known string constants. These are stored as plain text columns (not PG // enums) so new languages / modes / scopes can be added without a migration. // --------------------------------------------------------------------------- public static class WorkflowMode { public const string Function = "function"; public const string Durable = "durable"; public const string Handler = "handler"; } public static class WorkflowStatus { public const string Compiled = "compiled"; public const string Invalid = "invalid"; } public static class RunStatus { public const string Pending = "pending"; public const string Running = "running"; public const string Succeeded = "succeeded"; public const string Failed = "failed"; public const string Compensating = "compensating"; public const string Dead = "dead"; } public static class TaskRunStatus { public const string Pending = "pending"; public const string Running = "running"; public const string Succeeded = "succeeded"; public const string Failed = "failed"; public const string Compensated = "compensated"; public const string Dead = "dead"; } public static class TriggerType { public const string Cron = "cron"; public const string Interval = "interval"; public const string Webhook = "webhook"; public const string Event = "event"; // manual / external event public const string Queue = "queue"; // handler-mode stream subscription } public static class ApiKeyScope { public const string Manage = "manage"; public const string Run = "run"; public const string Read = "read"; } /// /// The kind of a workflow runtime (which workflow-api instance executes a /// tenant's workflows). /// public static class WorkflowRuntimeKind { /// The built-in platform runtime (multi-tenant, git/Forgejo-backed). public const string Platform = "platform"; /// A tenant-owned workflows-api instance (self-hosted, Lite mode). public const string SelfHosted = "self-hosted"; } public static class WorkflowRuntimeStatus { public const string Online = "online"; public const string Offline = "offline"; public const string Unknown = "unknown"; } /// /// A compiled workflow definition. The authoritative source is a YAML file in /// git; this row is the compiled snapshot keyed by git_sha. /// public class Workflow { public Guid Id { get; set; } public required string TenantId { get; set; } public required string Name { get; set; } public required string Path { get; set; } public string? GitSha { get; set; } /// /// The repository name (basename) this workflow was compiled from, e.g. /// workflows (default) or a user-selected repo like wiz4apps. /// The workflows module only lists / runs workflows whose /// matches the tenant's current workflow repo; switching repos "unloads" /// workflows from other repos without deleting their history. /// public string Repo { get; set; } = "workflows"; public required string Status { get; set; } // compiled | invalid public required string Mode { get; set; } // function | durable | handler public string? TriggerJson { get; set; } // jsonb: { type, cron, interval, webhookPath, stream } /// /// Whether the workflow's auto-trigger (cron/interval/webhook/handler) is /// enabled. Toggled from the Workflows UI; the trigger scheduler skips /// disabled workflows. Manual "run now" is unaffected. /// public bool TriggerEnabled { get; set; } = true; public required string Target { get; set; } // execution host /// /// User-facing build/version of the definition (e.g. "1.0.1"). Read from the /// YAML version: key when present (default "1.0.0"); the UI bumps the /// patch number on each save. /// public string Version { get; set; } = "1.0.0"; public DateTime? CompiledAt { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } public List Tasks { get; set; } = new(); } /// /// One node in a workflow's task tree. ParentId groups tasks for /// structure/Mermaid; execution follows NextId (success) and /// OnErrorId (failure). All three are soft references — resolved to task /// ids during YAML compilation and never foreign-key constrained. /// public class WorkflowTask { public Guid Id { get; set; } public Guid WorkflowId { get; set; } /// Original YAML task id ("validate", "enrich", …); "root" for the entry task. public required string Key { get; set; } public Guid? ParentId { get; set; } public Guid? NextId { get; set; } public Guid? OnErrorId { get; set; } public required string Language { get; set; } public required string Mode { get; set; } public string? EntryJson { get; set; } // jsonb: { file, function } public string? EnvJson { get; set; } // jsonb: { KEY: "value" } public int Order { get; set; } /// /// Managed-server id (or alias) the task's code is executed on via SSH. Null => /// run as a local subprocess (the default). Resolved at compile time from the /// task's server or the workflow header's server default. /// public string? Server { get; set; } /// /// Set when a task is removed from the workflow YAML (renamed/removed) so its /// historical rows are preserved instead of being wiped. /// Archived tasks are excluded from active-definition queries via a global query /// filter; sync re-activates a row whose id reappears in the YAML. /// public DateTime? ArchivedAt { get; set; } public Workflow Workflow { get; set; } = null!; } public class WorkflowRun { public Guid Id { get; set; } public Guid WorkflowId { get; set; } public required string TenantId { get; set; } public required string Status { get; set; } public string? TriggerJson { get; set; } public string? InputJson { get; set; } public string? OutputJson { get; set; } public string? Error { get; set; } public string? CorrelationId { get; set; } /// /// When set (durable resume), execution starts at this task instead of the /// root entry. Null for a normal run, which always starts at the root task. /// public Guid? StartTaskId { get; set; } /// /// Remaining saga compensation steps as a JSON array of /// { taskId, input }, in dispatch order. Only populated while the run /// is ; cleared when compensation ends. /// public string? CompensationPlanJson { get; set; } public DateTime? StartedAt { get; set; } public DateTime? FinishedAt { get; set; } public Workflow Workflow { get; set; } = null!; public List TaskRuns { get; set; } = new(); } public class TaskRun { public Guid Id { get; set; } public Guid RunId { get; set; } public Guid TaskId { get; set; } public int Attempt { get; set; } public required string Status { get; set; } public string? InputJson { get; set; } public string? OutputJson { get; set; } public string? Error { get; set; } public int RetryCount { get; set; } /// /// When a failed attempt is scheduled for retry, the earliest time the retry /// may be re-dispatched (exponential backoff). Null when no retry is pending. /// public DateTime? NextAttemptAt { get; set; } /// /// True when this task run is a saga compensation step (an onError /// target run to undo a completed task), not part of the success chain. /// public bool IsCompensation { get; set; } public DateTime? StartedAt { get; set; } public DateTime? FinishedAt { get; set; } public WorkflowRun Run { get; set; } = null!; public WorkflowTask Task { get; set; } = null!; } /// /// Checkpoint state for durable and handler workflows. Keyed by /// the long-lived instance id; enables resume via correlation id after an /// external event. /// public class DurableState { [Key] public required string InstanceId { get; set; } public Guid TaskId { get; set; } public string? StateJson { get; set; } public DateTime CheckpointAt { get; set; } public string? CorrelationId { get; set; } } /// /// Per-tenant workflow repo setting. Records which repository (basename) is the /// tenant's current workflow repo; the repository holds the workflow YAML + /// sibling code files. Defaults to the configured WorkflowSource:WorkflowRepoName /// ("workflows") when no row exists. /// public class TenantWorkflowRepo { public required string TenantId { get; set; } public required string RepoName { get; set; } public DateTime? UpdatedAt { get; set; } } /// /// Per-tenant operator API key. Only the SHA-256 hash is stored; the raw key is /// shown once at mint time. /// public class ApiKey { public Guid Id { get; set; } public required string TenantId { get; set; } public required string KeyHash { get; set; } public required string Label { get; set; } public string? ScopesJson { get; set; } // jsonb array: ["manage","run","read"] public DateTime CreatedAt { get; set; } public DateTime? LastUsedAt { get; set; } public DateTime? RevokedAt { get; set; } } /// /// A workflow runtime — a workflow-api instance that can execute a tenant's /// workflows. There is always one virtual /// runtime per tenant (the built-in multi-tenant engine); a tenant may additionally /// connect instances. /// /// Connectivity model: /// - platform: local, git/Forgejo-backed, shared filesystem (no endpoint). /// - self-hosted (inbound/legacy): + /// — the tenant exposes a public URL and the platform calls it directly. /// - self-hosted (outbound/runner): only — the runtime /// dials out to the platform over WebSocket and holds the channel; no inbound URL. /// public class WorkflowRuntime { public Guid Id { get; set; } public required string TenantId { get; set; } /// User-facing name (e.g. "Platform" or "my-server"). public required string Label { get; set; } /// : "platform" | "self-hosted". public required string Kind { get; set; } /// Legacy/inbound URL; null for the platform runtime or outbound-only runtimes. public string? Endpoint { get; set; } /// SHA-256 hash of the inbound API key (legacy migration path). public string? ApiKeyHash { get; set; } /// SHA-256 hash of the outbound channel secret (runner model). public string? SecretHash { get; set; } /// : "online" | "offline" | "unknown". public string Status { get; set; } = WorkflowRuntimeStatus.Unknown; public DateTime? LastSeenAt { get; set; } public string? LastError { get; set; } /// Cached self-info json: { name, version, startedAt, multiTenant }. public string? InfoJson { get; set; } /// /// True for the tenant's default self-hosted runtime (used when the /// user has not picked a specific self-hosted runtime). The platform runtime /// is the implicit global fallback (identified by ), not a /// default among self-hosted runtimes, so its is false. /// public bool IsDefault { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } }