w4c-workflows-api/Models/Entities.cs

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