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(); /// /// Node-mode connections between tasks (from output port to input port). /// Legacy script workflows have no rows here and keep using NextId. /// public List TaskEdges { 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; } // ---- node-mode columns ------------------------------------------------- // A task is either a script task (legacy: Language + EntryJson, driven by the // NextId chain) or a node task (NodeType + ParametersJson, driven by // WorkflowTaskEdge rows). The two modes never mix within one workflow. /// Catalog blueprint type, e.g. core.set. Null for script tasks. public string? NodeType { get; set; } /// Pinned blueprint version (the resolved default when null/0). public double? NodeVersion { get; set; } /// jsonb: the node's raw (unresolved) parameters. public string? ParametersJson { get; set; } /// jsonb array: credential aliases the node resolves at run time. public string? CredentialsJson { get; set; } /// jsonb: the node's retry policy. public string? RetryJson { get; set; } /// Route node failures to the error port instead of failing the run. public bool ContinueOnFail { get; set; } /// Overrides the blueprint run mode: eachItem | allItems. public string? RunMode { 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!; } /// /// A connection between two node-mode tasks. / /// are soft references (like NextId): a removed /// task is archived, not deleted, so the FKs can't be constrained without /// blocking history. Ports are 0-based indexes into the blueprint's /// input/output lists. /// public class WorkflowTaskEdge { public Guid Id { get; set; } public Guid WorkflowId { get; set; } /// Source task id. public Guid FromTaskId { get; set; } /// Source output port index. public int FromOutput { get; set; } /// Target task id. public Guid ToTaskId { get; set; } /// Target input port index. public int ToInput { get; set; } /// True when this edge closes a loop (targets a loop-capable node). public bool IsLoopBack { get; set; } public Workflow Workflow { get; set; } = null!; } /// /// A tenant-scoped credential for node executors. The secret payload is stored /// as ciphertext () and only ever decrypted in /// memory during a run; it is never persisted in run history. /// public class Credential { public Guid Id { get; set; } public required string TenantId { get; set; } public required string Name { get; set; } /// Credential type id from the credential catalog, e.g. httpHeaderAuth. public required string Type { get; set; } /// Ciphertext of the credential's JSON data (see ICredentialCipher). public required string EncryptedData { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } } 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; } // ---- sub-workflow plumbing -------------------------------------------- // A run started by a core.executeWorkflow node is a child run: it points at // the run that spawned it and at the calling task, and records its nesting // depth. All three are soft references (like NextId/TaskId elsewhere), so a // parent/child whose workflow was later removed still resolves for history. /// Run that spawned this one via a sub-workflow node; null for a top-level run. public Guid? ParentRunId { get; set; } /// The calling node's ; null for a top-level run. public Guid? ParentTaskId { get; set; } /// Sub-workflow nesting depth: 0 for a top-level run, +1 per nested call. public int Depth { 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 workflow execution counter for one quota period (a calendar month, /// UTC). Incremented once per top-level run — a sub-workflow child run spawned by /// a core.executeWorkflow node is part of its parent's execution and is not /// counted separately. The row is append-only in practice: clearing run history /// does NOT reset it, so history cleanup can never be used to regain quota. /// public class WorkflowUsage { public required string TenantId { get; set; } /// Start of the quota period (first instant of the calendar month, UTC). public DateTime PeriodStart { get; set; } /// Top-level executions started in this period. public long RunsUsed { 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; } }