ores.workflow.workflow_instance

Table of Contents

Tracks the lifecycle of a workflow execution, including its type, the entity it acts on, its status, the serialised request that triggered it, and any result or error produced. Instances are append-mostly; status transitions are the primary mutation.

1. Flags

2. Natural keys

3. Columns

3.1. id

UUID primary key for the workflow instance.

3.2. type

Workflow type name, e.g. 'provision_tenant_workflow'.

3.3. target_kind

What the run acts on, named by the caller: 'tenant' for a run that provisions a tenant. The engine stores it and never interprets it, so a component states its own kind without the engine learning that component's vocabulary.

A run that acts on no entity leaves it empty.

3.4. target_id

Identity of the entity the run acts on, in the kind target_kind states. It is not a foreign key: the engine cannot hold one to every component's tables, so it is a soft reference, the way a status row names its badge.

The target is not the owner. tenant_id states the tenant whose work the run is, which scopes the run and governs who reads it. The target states what the run works on, which may be a different tenant entirely. Provisioning is the case that makes the difference plain: the run belongs to the tenant that asked for it, so that tenant can follow its own work, and it acts on the tenant it creates, which cannot read the run because its session does not exist yet.

3.5. state_id

FK to the FSM state record (ores_workflow_fsm_states_tbl) for this instance.

3.6. request_json

Serialised JSON payload of the originating request.

3.7. result_json

Serialised JSON result, populated on successful completion.

3.8. error

Human-readable error message, populated on failure.

3.9. correlation_id

Distributed tracing correlation ID (Nats-Correlation-Id header value).

3.10. created_by

Username or service identity that initiated the workflow.

3.11. step_count

How many steps the run's chain holds, fixed when the run starts. Nothing advances it, and a run's chain never changes, so this is a fact about the run rather than a cursor over it. It is stored so that a list of runs can say how long each one is without reading every chain.

3.12. completed_at

Timestamp when the workflow reached a terminal state. The engine writes it from its state funnel, so a caller editing the run never states it.

3.13. last_event_at

Timestamp of the run's most recent activity: every state transition, and every dispatch the run does between two of them. The engine writes it, so a caller editing the run never states it.

4. SQL

4.1. Flags

5. Indexes

The list query filters by state and orders by type, and a lookup names one instance, so the tenant index and the tenant-scoped unique index are the hot paths. The engine's own list read is not: it spans every tenant, so it cannot use a leading tenant_id, and it reads the open rows in one pass at startup. That pass is bounded by the runs in flight rather than by the table, so a scan is the honest cost of recovering all of them. The target has no index yet: the list read filters by target in memory, after the tenant's rows are read, so an index would cost every write and help no read. It comes with a repository read that filters by target.

5.1. tenant_id

5.2. state_id

5.3. tenant_type

6. C++

6.1. Flags

6.2. Repository

Reads rely on the row-level security policy instead of an application-level tenant_id predicate. That policy admits a row to its own tenant, and to a system-tenant session it admits every tenant's rows.

The engine is a cross-tenant service. It holds the system tenant and drives runs that belong to whoever asked for them, so a tenant_id = ctx.tenant_id() predicate on top of the policy hides every other tenant's run from the engine. A run it cannot read back is a run it cannot advance or recover: the step insert is refused, the published marker and the step progress become silent no-ops, and the completion event finds no step. A tenant session still reads only its own rows, because the policy is what draws that line.

6.3. Domain includes

#include <optional>
#include <string>
#include <boost/uuid/uuid.hpp>

6.4. Conventions

6.5. Custom repository methods

7. See also

Emacs 29.3 (Org mode 9.6.15)