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; }
}