220 lines
8.2 KiB
C#
220 lines
8.2 KiB
C#
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";
|
|
}
|
|
|
|
/// <summary>
|
|
/// A compiled workflow definition. The authoritative source is a YAML file in
|
|
/// git; this row is the compiled snapshot keyed by <c>git_sha</c>.
|
|
/// </summary>
|
|
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; }
|
|
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 }
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public bool TriggerEnabled { get; set; } = true;
|
|
public required string Target { get; set; } // execution host
|
|
/// <summary>
|
|
/// User-facing build/version of the definition (e.g. "1.0.1"). Read from the
|
|
/// YAML <c>version:</c> key when present (default "1.0.0"); the UI bumps the
|
|
/// patch number on each save.
|
|
/// </summary>
|
|
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<WorkflowTask> Tasks { get; set; } = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// One node in a workflow's task tree. <c>ParentId</c> groups tasks for
|
|
/// structure/Mermaid; execution follows <c>NextId</c> (success) and
|
|
/// <c>OnErrorId</c> (failure). All three are soft references — resolved to task
|
|
/// ids during YAML compilation and never foreign-key constrained.
|
|
/// </summary>
|
|
public class WorkflowTask
|
|
{
|
|
public Guid Id { get; set; }
|
|
public Guid WorkflowId { get; set; }
|
|
/// <summary>Original YAML task id ("validate", "enrich", …); "root" for the entry task.</summary>
|
|
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; }
|
|
|
|
/// <summary>
|
|
/// 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 <c>server</c> or the workflow header's <c>server</c> default.
|
|
/// </summary>
|
|
public string? Server { get; set; }
|
|
|
|
/// <summary>
|
|
/// Set when a task is removed from the workflow YAML (renamed/removed) so its
|
|
/// historical <see cref="TaskRun"/> 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.
|
|
/// </summary>
|
|
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; }
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public Guid? StartTaskId { get; set; }
|
|
/// <summary>
|
|
/// Remaining saga compensation steps as a JSON array of
|
|
/// <c>{ taskId, input }</c>, in dispatch order. Only populated while the run
|
|
/// is <see cref="RunStatus.Compensating"/>; cleared when compensation ends.
|
|
/// </summary>
|
|
public string? CompensationPlanJson { get; set; }
|
|
public DateTime? StartedAt { get; set; }
|
|
public DateTime? FinishedAt { get; set; }
|
|
|
|
public Workflow Workflow { get; set; } = null!;
|
|
public List<TaskRun> 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; }
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public DateTime? NextAttemptAt { get; set; }
|
|
/// <summary>
|
|
/// True when this task run is a saga compensation step (an <c>onError</c>
|
|
/// target run to undo a completed task), not part of the success chain.
|
|
/// </summary>
|
|
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!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checkpoint state for <c>durable</c> and <c>handler</c> workflows. Keyed by
|
|
/// the long-lived instance id; enables resume via correlation id after an
|
|
/// external event.
|
|
/// </summary>
|
|
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; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Per-tenant operator API key. Only the SHA-256 hash is stored; the raw key is
|
|
/// shown once at mint time.
|
|
/// </summary>
|
|
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; }
|
|
}
|