Workflow Architecture
Verified against commit
942a18727c(2026-09-26, DOC-2 of SPRINT_20260926_001). Re-verify by reading the evidence paths listed in the Evidence section.
Service wiring (image, database and role, compose profile, bundle presence) is generated in the service inventory; this dossier does not restate it.
Scope. Architecture and operational contract for the Workflow Engine: its HTTP API, authorization model, PostgreSQL persistence, signal pump, and graph renderer. This dossier is for service implementers and for operators running or troubleshooting workflow instances (including the signal dead-letter runbooks).
Implementation status (owner ruling 2026-08-19; synchronized 2026-08-21): kept for future use, service dormant. Owner-input register item 17 retains the Workflow engine, storage backends and WebService project as supported source. That ruling is retention, not activation. The canonical compose stack does not deploy
StellaOps.Workflow.WebService, Router has neither a/api/workflowroute nor the former^/api/v1/workflows(.*)alias, and ReleaseOrchestrator/Scheduler callers are absent. The alias targeted ReleaseOrchestrator even though that service exposed no matching handler; RAR-5 removed it from the gateway source in41b205cc81on 2026-08-11. It is no longer a configured surface. Reactivation requires a new explicitly scoped implementation/deployment sprint: P4 ownership of a Workflow database + role on the shared PostgreSQL installation, in-service startup migrations, canonical scopes and client grants, authenticated callers, compose wiring, and a gateway route. None of those are implied by the keep ruling or retained source. The W3-15 disposition and its deferred residue/drop decision are recorded inSPRINT_20260722_020.
The retained Workflow Engine is a general-purpose runtime for definition deployment, instance lifecycle, signal-driven task transitions, dead-letter handling, and authorization-gated task progression. StellaOps.Workflow.WebService/Program.cs still contains Router integration and HTTP host composition so the source remains buildable, but that composition is not current deployment topology.
Retained HTTP API Surface
If the dormant WebService is hosted, its business endpoints are mapped under the /api/workflow route group (WorkflowEndpoints.MapWorkflowEndpoints). Every endpoint requires a named authorization policy (see “Authorization & Scopes”); state-changing endpoints additionally emit an audit record via .Audited(AuditModules.Workflow, …).
| Method & Path | Handler | Policy | Audited |
|---|---|---|---|
POST /api/workflow/start | StartWorkflow | Operate | StartInstance |
GET /api/workflow/instances/{id} | GetInstance | View | — |
GET /api/workflow/instances | GetInstances | View | — |
GET /api/workflow/tasks/{id} | GetTask | View | — |
GET /api/workflow/tasks | GetTasks | View | — |
POST /api/workflow/tasks/{id}/complete | CompleteTask | Operate | CompleteTask |
POST /api/workflow/tasks/{id}/assign | AssignTask | Operate | AssignTask |
POST /api/workflow/tasks/{id}/release | ReleaseTask | Operate | ReleaseTask |
POST /api/workflow/signals/raise | RaiseSignal | Operate | RaiseSignal |
GET /api/workflow/definitions | GetDefinitions (in-memory catalog) | View | — |
GET /api/workflow/definitions/{id} | GetDefinitionById (store-backed) | View | — |
GET /api/workflow/definitions/{id}/render-graph | GetRenderGraph | View | — |
POST /api/workflow/definitions/import | ImportDefinition | Admin | ImportDefinition |
POST /api/workflow/definitions/export | ExportDefinition | View | — |
POST /api/workflow/definitions/import-multi | ImportDefinitionMulti | Admin | ImportDefinitionMulti |
POST /api/workflow/definitions/export-multi | ExportDefinitionMulti | View | — |
GET /api/workflow/supported-formats | GetSupportedFormats | View | — |
GET /api/workflow/definitions/{name}/versions | GetDefinitionVersions | View | — |
POST /api/workflow/definitions/{name}/activate | ActivateDefinition | Admin | ActivateDefinition |
GET /api/workflow/diagrams/{name} | GetDiagram | View | — |
GET /api/workflow/canonical/schema | GetCanonicalSchema | View | — |
POST /api/workflow/canonical/validate | ValidateCanonical | View | — |
GET /api/workflow/functions | GetFunctionCatalog | View | — |
GET /api/workflow/metadata | GetServiceMetadata | View | — |
POST /api/workflow/retention/run | RunRetention | Admin | RunRetention |
GET /api/workflow/signals/dead-letters | GetDeadLetters | View | — |
POST /api/workflow/signals/dead-letters/replay | ReplayDeadLetters | Admin | ReplayDeadLetters |
GET /api/workflow/signals/pump/stats | GetSignalPumpStats | View | — |
The retained host also maps operational/diagnostic endpoints outside the group: anonymous GET /healthz (liveness, Predicate => false), anonymous GET /readyz (readiness), the build-info endpoint (BuildInfoEndpointExtensions.MapBuildInfoEndpoint, fallback module workflow), and the OpenAPI document (MapOpenApi().AllowAnonymous()).
The POST /start request body is StartWorkflowRequest (WorkflowName required; WorkflowVersion, BusinessReference, and a Payload dictionary optional) and returns StartWorkflowResponse (WorkflowInstanceId, resolved WorkflowName/WorkflowVersion, NextTasks, WorkflowState). GET /instances and GET /tasks accept their filters as query parameters (workflowName, workflowVersion, workflowInstanceId, businessReferenceKey, status, assignee, includeDetails, actorId). RunRetention is the only endpoint that reads the wall clock (DateTime.UtcNow, explicitly annotated Determinism:Allowed) when no ReferenceUtc override is supplied.
Authorization & Scopes
The dormant WebService source uses a three-tier scope hierarchy defined locally in StellaOps.Workflow.WebService.Authorization.WorkflowPolicies. These scope strings are deliberately not registered in the canonical StellaOps.Auth.Abstractions.StellaOpsScopes catalog and no current client is granted them. They describe the retained endpoint policy shape, not a grantable production contract:
| Scope constant | Value | Grants |
|---|---|---|
ViewScope | workflow:view | read-only access to definitions, instances, tasks, signals |
OperateScope | workflow:operate | start workflows; complete/assign/release tasks; raise signals (implies view) |
AdminScope | workflow:admin | deploy/activate definitions; run retention; replay dead letters (implies operate + view) |
Named policies registered via AddWorkflowPolicies:
View(workflow.view) — satisfied by any ofworkflow:view,workflow:operate,workflow:admin.Operate(workflow.operate) — satisfied byworkflow:operateorworkflow:admin.Admin(workflow.admin) — satisfied only byworkflow:admin.
HasAnyScope reads both space-delimited scope claims (OAuth standard form, StellaOpsClaimTypes.Scope) and individual scp item claims (StellaOpsClaimTypes.ScopeItem), case-insensitively.
On any future reactivation, the gateway must remain the primary trust anchor: it validates bearer tokens, strips them, and forwards a signed identity envelope. The retained service verifies that envelope via UseIdentityEnvelopeAuthentication() and the WorkflowEnvelopeAuthenticationHandler authentication scheme, then runs the standard UseAuthentication()/UseAuthorization() pipeline. The defense-in-depth RequireAuthenticatedUser gates reject any direct in-cluster access that bypasses the gateway with 401. Do not restore callers using anonymous direct HTTP or network bypasses; provision a scoped service identity and signed gateway envelope.
PostgreSQL Persistence Schema
The Postgres backend’s tables are created by the forward-only migration chain embedded in StellaOps.Workflow.Persistence (<EmbeddedResource Include="Migrations/*.sql" />) and applied on startup via AddStartupMigrations (schema workflow, module name Workflow.DataStore.PostgreSQL), registered by AddWorkflowPostgresDataStore.
001_v1_workflow_datastore_postgresql_baseline.sql— the pre-1.0 collapsed baseline. It createswf_instances,wf_tasks,wf_task_events,wf_runtime_states,wf_host_locks,wf_signal_queue,wf_signal_dead_letters,wf_signal_wake_outbox, andwf_definitions.002_workflow_retention_classes.sql— comment-only migration (RFC-14 owner ruling Q-45, 2026-09-23) that records the retention class of each of the nine tables (see “Retention”).- The former
001_initial_schema.sqland002_wf_definitions.sqlare preserved under the runtime backend’sMigrations/_archived/pre_1.0/mig061/as historical inputs; that backend embeds no SQL.
Fixed names (Q-45). The migration chain is the only DDL authority. The schema and the table names are not configurable: WorkflowTables holds the fixed, schema-qualified names (workflow.wf_instances, and so on), every store statement names its table literally, and PostgresWorkflowSqlBuilder no longer emits DDL. The former SchemaName, ConnectionStringName and eight *TableName options of PostgresWorkflowBackendOptions are removed. SRD_WFKLW is a separate legacy Oracle backend schema and is not a PostgreSQL name. LISTEN/NOTIFY channels default to workflow_signal (NotifyChannel) and workflow_signal_wake_outbox (WakeOutboxNotifyChannel).
Re-verify with the PostgreSQL backend test project, including WorkflowMigrationStartupTests (nonempty discovery, complete-ledger restart parity) and WorkflowRetentionClassificationTests. This source change does not activate the dormant service.
Retention
002_workflow_retention_classes.sql classifies the tables from their writers:
| Class | Tables | Lifecycle |
|---|---|---|
| operational-state | wf_instances, wf_tasks, wf_task_events, wf_runtime_states | purge_after_utc is set only when an instance or task completes (WorkflowRetention:CompletedPurgeAfterDays, default 180). The WorkflowRetentionService, run by the retention hosted job (WorkflowRetentionHostedJob:Interval, default 24 h), deletes expired instances, their tasks and task events, and the runtime states of the purged instances. Open instances are never deleted. |
| source-of-truth | wf_definitions, wf_signal_queue, wf_signal_dead_letters, wf_signal_wake_outbox, wf_host_locks | Deleted only by the domain operation the row describes (delivery, dead-letter replay, wake completion, lock release); never by age. Definitions are never deleted. |
AddWorkflowPostgresDataStore also registers the Doctor check doctor.workflow.retention.overdue-purge (WorkflowRetentionBacklogCheck, category Capacity, default severity Warning). It reports rows whose purge_after_utc passed more than two retention intervals ago, and completed rows retained inside the window against a budget (Doctor:Workflow:MaximumRetainedTerminalRows, default 1,000,000). The retained WebService does not expose a doctor-check surface, so the check runs only in a host that adopts one.
Configuration Keys
| Section / key | Default | Purpose |
|---|---|---|
WorkflowBackend:Provider | Retained WebService config: Postgres | Backend selection: Oracle, Postgres, or Mongo (WorkflowBackendNames). The shared abstraction retains a legacy Oracle fallback, but the Stella WebService configuration explicitly selects Postgres. |
STELLAOPS_POSTGRES_WORKFLOW_CONNECTION | none (required) | The Workflow service’s own database connection. AddWorkflowPostgresDataStore throws when it is absent or blank; there is no ConnectionStrings:* or generic fallback and no DSN ships with the product. |
WorkflowBackend:Postgres:*PoolSize / Pooling / ConnectionIdleLifetimeSeconds | 1 / 100 / true / 300 | Npgsql pooling. |
WorkflowBackend:Postgres:ClaimBatchSize / ClaimTimeoutSeconds / BlockingWaitSeconds | 32 / 60 / 30 | Native-driver signal claim tuning. |
WorkflowBackend:Postgres:NotifyChannel / WakeOutboxNotifyChannel | workflow_signal / workflow_signal_wake_outbox | LISTEN/NOTIFY channel names. |
WorkflowRetention:CompletedPurgeAfterDays / OpenStaleAfterDays | 180 / 30 | Purge window for completed instances; stale threshold for open ones. |
WorkflowRetentionHostedJob:Enabled / Interval / InitialDelay / LockName | true / 24:00:00 / 00:05:00 / workflow.retention | Retention hosted job. |
Doctor:Workflow:MaximumRetainedTerminalRows | 1000000 | Budget for doctor.workflow.retention.overdue-purge. |
WorkflowSignalDriver:Provider | Native | Signal driver: Native (Postgres LISTEN/NOTIFY) or Redis (WorkflowSignalDriverNames). |
WorkflowAq:MaxDeliveryAttempts | 10 | Worker retry budget (see “Signal pump retry budget”). |
WorkflowAq:DeadLetterQueueName | WF_DLQ_Q | User dead-letter queue for the Oracle AQ path. |
WorkflowAq:SignalQueueName / ScheduleQueueName | WF_SIGNAL_Q / WF_SCHEDULE_Q | Oracle AQ queue names. |
WorkflowAq:ConsumerName / BlockingDequeueSeconds | WORKFLOW_SERVICE / 30 | Oracle AQ consumer + dequeue wait. |
ConnectionStrings__WorkflowOracle | — | Required environment variable for EF design-time Oracle factories; no source fallback is provided. Runtime DI reads ConnectionStrings:WorkflowOracle, then Default. |
WorkflowRendering / WorkflowRenderLayoutProviderNames | ElkSharp | Renderer provider (ElkSharp, ElkJs, Msagl). |
Router | — | Stella Router microservice integration section. |
WorkflowRuntimeProviderNames exposes the two runtime-provider identifiers used in persisted state: Stella.InProcess and Stella.Engine.
Runtime Persistence Contract
If reactivated, Workflow WebService runs against PostgreSQL-backed runtime stores outside Development and Testing. WorkflowBackend:Provider must be Postgres, and STELLAOPS_POSTGRES_WORKFLOW_CONNECTION must name the Workflow service’s own database so startup migrations can converge the workflow schema before request handling (the host fails closed without it; WorkflowConnectionStringFailClosedTests). No current canonical host starts this WebService.
Production startup rejects authoritative local fallbacks for runtime state, hosted-job locks, definition storage, signal stores, signal drivers, wake outbox, and signal dead letters. The engine-level null and in-memory store implementations remain available only for explicit local/test harnesses.
Workflow transport defaults are not mocks. When no HTTP, Rabbit, legacy Rabbit, GraphQL, or microservice transport plugin is registered, the engine binds explicit unconfigured transports that return failed execution responses naming the missing plugin. They do not enqueue, call, or acknowledge work, so production workflows fail closed until the required transport plugin is registered by the host.
Definition imports fail closed when no backend-specific definition store is registered. The null definition store never acknowledges writes or activations because that would make workflow deployment look successful without persisting deployable definitions.
PostgreSQL Runtime Bindings
When the Postgres backend is active, the WebService uses PostgreSQL implementations for runtime state, hosted-job locks, projections, definition storage, signal store/claims/scheduler, wake outbox sender/receiver, and signal dead-letter storage. The native signal driver is enabled by WorkflowSignalDriver:Provider = Native.
Renderer Layout Engines
Workflow definitions are visualised through IWorkflowRenderLayoutEngine providers. Three implementations are wired in this repo:
StellaOps.Workflow.Renderer.ElkSharp— pure C# port of the Eclipse Layout Kernel (StellaOps.ElkSharp). Runs in-process; no JVM, no IPC, no subprocess.StellaOps.Workflow.Renderer.ElkJs— shells out to a Node.js script bundled undersrc/Workflow/__Libraries/StellaOps.Workflow.Renderer.ElkJs/tools/elk-layout/that drives the upstreamelkjspackage. The wrapper performs an idempotentnpm cion first use guarded by aSemaphoreSlim(no per-call install). Each layout call writes one JSON input file, spawns onenodeprocess per call, and reads one JSON output file.StellaOps.Workflow.Renderer.Msagl— Microsoft Automatic Graph Layout, in-process .NET.
Lifecycle decision (sprint SPRINT_20260429_005_Workflow_renderer_jvm_lifecycle.md)
The sprint was created on a hypothesis that StellaOps.ElkSharp wraps a JVM child process (the upstream Eclipse project ELK is implemented in Java) and that the renderer test host crashed because of JVM forking, deadlocked stdin/stdout buffers, or unhandled JVM aborts. The hypothesis is incorrect: StellaOps.ElkSharp is a pure C# port of the algorithm, not a wrapper. There is no JVM, no Java process, no IPC, and no subprocess in the ElkSharp call path.
The renderer test host did not crash. It was hanging because a single Best-effort layout of the canonical 24-node DocumentProcessingWorkflow takes ~60 s in Release on a typical dev box (the iterative-routing path explores many candidate strategies). The renderer test suite contained several fixtures that performed dozens of such layouts back-to-back, far exceeding VSTest’s default --blame-hang-timeout (3 min). VSTest then collected a hangdump of the alive-but-busy worker process and reported “test host crashed.”
Decisions (no JVM-worker pattern was needed):
- ElkSharp remains pure in-process. No long-lived worker, no IPC, no JVM bring-up. Each
LayoutAsynccall is a regular method invocation against anElkSharpLayeredLayoutEngineinstance. - The renderer test fixtures that drive the 24-node
Best-effort layout (DocumentProcessingWorkflowRender,DocumentProcessingWorkflowRenderBenchmark,DocumentProcessingWorkflowRenderingTests,RenderReviewHarness,RenderReviewAnalyzer) are marked[Explicit]so they are excluded from the default suite. They remain runnable on demand via NUnit class/name filters. - A standalone reproducer at
src/Workflow/__Libraries/StellaOps.Workflow.Renderer.ElkJs/tools/elk-stressexercises ElkSharp’s full call path outside the test runner. It serves both as the diagnostic harness for the original sprint and as a performance regression guard for future ElkSharp changes. - The renderer CI lane (
.gitea/workflows/workflow-renderer.yml) runs the default test suite on every relevant PR with--blame --blame-hang-timeout=180000and fails the build if any hangdump is captured. A separate nightly job runs the[Explicit]fixtures and the elk-stress harness.
There is no Java runtime requirement on the production host for ElkSharp rendering. The ElkJs provider needs Node.js available on the host that runs the renderer. ElkSharp is the default provider used by the engine and tests.
ElkSharp edge-correctness contracts
Sprints SPRINT_20260430_081_Workflow_renderer_elksharp_correctness.md and SPRINT_20260501_053_Workflow_elksharp_aspirational_helpers_followup.md define the current edge-routing contracts for the in-process ElkSharp provider.
- Boundary-slot assignments are the canonical lattice for source exits and target entries. Late cleanup phases must preserve already-assigned slot points when they are still on the owning node boundary, and singleton edges fall back to the owning face’s discrete slot instead of keeping arbitrary off-lattice coordinates.
- Gateway source exits and gateway target entries intentionally use different validity rules. Source exits must leave from a face interior and are repaired away from left/right diamond tip vertices. Target entries may converge at left/right tip vertices only when the target-specific boundary-angle validator accepts the approach. Generic gateway boundary-angle checks still reject tip vertices.
- Mixed-face, source-departure, target-approach, and under-node repair phases are stabilization passes over the same boundary-slot contract. A repair is accepted only when it reduces the targeted violation family without reintroducing source-exit, boundary-angle, crossing, or under-node defects.
- Protected under-node candidates are still eligible for final detour closure after the protected edge no longer has an under-node violation. The deterministic renderer tests use synthetic fixtures for that contract; slow document-processing render reviews remain explicit/on-demand harnesses.
ElkEdgeNodeAvoidance.ShiftSectionruns a post-shift visual-clearance nudge after the free- interval shift. The free-interval shift only considers peers that are primary-adjacent (withinPrimaryAdjacencyMargin = 8 pxalong the segment’s primary axis); peers outside that window can still have a cross boundary close to the shifted coord. The nudge loop walks the same coord outward in the shift direction until it clears every peer’s cross boundary by at least the visual margin (DefaultMargin = 12 px). Source-exit and target-entry endpoints stay anchored to their owning node boundary regardless of the nudge.
Operational notes
- Hangdumps from the renderer CI lane are uploaded as artifacts under
renderer-tests-hangdumps. The build fails when one is captured so a perf regression in ElkSharp’s iterative-routing budget cannot silently push the suite past the blame timeout again. - For incident response on a renderer hang locally, repro with:
dotnet run --project src/Workflow/__Libraries/StellaOps.Workflow.Renderer.ElkJs/tools/elk-stress -- --iterations 5 --effort Best --direction TopToBottom. If a single iteration takes >2 min in Release, ElkSharp’s iterative-routing has regressed and needs profiling against the canonical document-processing graph.
Signal pump retry budget (WorkflowSignalDeliveryAttemptTracker)
The signal pump worker (StellaOps.Workflow.Engine.HostedServices.WorkflowSignalPumpWorker) owns the authoritative retry budget for workflow signal envelopes. Sprint SPRINT_20260430_080_Workflow_oracle_aq_retry_budget reworked the budget semantics after the original implementation relied on the transport-reported IWorkflowSignalLease.DeliveryCount, which is not authoritative for Oracle AQ under the worker’s Visibility = OnCommit rollback flow.
Semantics:
- The worker tracks attempts per
WorkflowSignalEnvelope.SignalIdin a per-processWorkflowSignalDeliveryAttemptTracker(DI-singleton). On eachRunOnceAsyncdispatch the tracker increments the count for the envelope’sSignalIdand returns the new value. - When the count reaches
WorkflowAq:MaxDeliveryAttemptsand the processor throws, the worker callsIWorkflowSignalLease.DeadLetterAsync(), which forwards the message to the configured user dead-letter queue (WorkflowAq:DeadLetterQueueName) and forgets the tracker entry. - On successful processing or a
WorkflowRuntimeStateConcurrencyExceptionskip, the tracker entry is forgotten so a future re-publish of the same signal id starts again from attempt 1. lease.DeliveryCountis now informational only; transports report their best-effort view (Oracle AQ surfacesDequeueAttempts, Redis surfaces a driver-defined value), but the budget no longer depends on monotonic agreement between transport and worker.
Cross-process safety net:
- The tracker is per-process; counts reset on host restart. Oracle AQ’s queue-side
max_retries(default 5) acts as the cross-host safety net so a poison message that survives a worker restart is eventually moved to Oracle’s internal exception queue (AQ$_<table>_E) regardless of in-process state. The user-defined dead-letter queue (WorkflowAq:DeadLetterQueueName) is populated only when the worker reaches its budget; Oracle’s automaticmax_retrieshandoff targets the AQ-internal exception queue, not the user DLQ. Operators should monitor BOTH the user DLQ and the AQ-internal exception queue when investigating stuck signals.
Configuration binding:
- Both
StellaOps.Workflow.Engine.Hosting.WorkflowAqOptions(consumed by the pump and hosted service) andStellaOps.Workflow.Signaling.OracleAq.WorkflowAqOptions(consumed by the transport, signal bus, schedule bus, and dead-letter store) bind from theWorkflowAqconfiguration section. Prior to sprintSPRINT_20260430_080only the signaling-side copy was bound, so host-supplied values forMaxDeliveryAttempts,BlockingDequeueSeconds, andConsumerNamesilently fell back to type-defaults at the worker layer. The duplicate option class is documented as an open item at the top ofEngine/Hosting/WorkflowAqOptions.cs; consolidation is deferred.
Operator runbook for inspecting stuck signals:
- User DLQ inspection (worker-driven dead-letters):
SELECT msgid, corrid, enq_time, retry_count FROM <schema>.<DeadLetterQueueTableName> ORDER BY enq_time DESC; - Oracle AQ-internal exception queue inspection (max_retries handoff):
-- Oracle stores the queue's exception queue under AQ$_<table>_E by default. -- Confirm via DBA_QUEUE_TABLES. SELECT msgid, corrid, msg_state, retry_count, enq_time FROM <schema>.AQ$_<SignalQueueTableName>_E ORDER BY enq_time DESC; - Cross-reference
corrid(which the engine sets toWorkflowSignalEnvelope.SignalId) withwf_signal_dead_lettersruntime store rows to identify the workflow instance that owns the stuck signal. Replay via thePOST /api/workflow/signals/dead-letters/replayendpoint (Admin scope), which callsWorkflowSignalDeadLetterService.ReplayAsync(WorkflowSignalDeadLetterReplayRequest), once the underlying processor failure is resolved.
Oracle/Redis integration test fixture
The Workflow Oracle data store and Oracle AQ signaling are exercised end-to-end by StellaOps.Workflow.DataStore.Oracle.Tests via WorkflowOracleRedisFixture (sprint SPRINT_20260429_004_Workflow_oracle_test_fixture.md, D-009). The fixture spawns one Oracle Free 23 slim-faststart container and one Redis 7-alpine container per test run (NUnit [SetUpFixture] lifetime), and exposes a BuildServiceProvider(...) helper that mirrors the production host’s DI wiring with the Oracle backend selected.
Key wiring decisions encoded in the fixture, recorded here so they persist beyond the originating sprint:
OracleManagedAqTransportreadsConnectionStrings:DefaultConnectiondirectly whileAddWorkflowOracleDataStorereadsConnectionStrings:WorkflowOracle(thenDefault). Production hosts must seed all three keys with the Oracle connection string.OracleManagedAqTransportinjects the baseMicrosoft.EntityFrameworkCore.DbContexttype, butAddDbContext<WorkflowDbContext>only registers the concrete subclass. Hosts wiring Oracle AQ must alias the base type:services.AddScoped<DbContext>(sp => sp.GetRequiredService<WorkflowDbContext>()).AddWorkflowRedisSignalingconsumesIConnectionMultiplexerbut does not own its lifetime. Hosts must register one, typically as a singleton bound to the configured Redis endpoint.
CI lane: .gitea/workflows/workflow-oracle-tests.yml runs the suite on PRs that touch the Oracle/Redis libraries or tests, with cached docker save archives keyed on the pinned image tags so cold-cache pulls happen once per tag bump rather than per PR.
Test-pattern reference and per-test cleanup discipline: see “Oracle / Redis test fixtures” in .memory/standards/TESTING_PRACTICES.md.
Evidence
| Claim | Source |
|---|---|
| Dormant: no compose service, bundle entry or Router route | devops/compose/docker-compose.stella-services.yml, devops/release/bundle/docker-compose.yml (no workflow service); src/Workflow/AGENTS.md |
| HTTP host, health, build-info, OpenAPI, Router integration | src/Workflow/StellaOps.Workflow.WebService/Program.cs |
/api/workflow endpoint group, policies, audit actions | src/Workflow/StellaOps.Workflow.WebService/Endpoints/WorkflowEndpoints.cs |
| Local scopes and policies (not in the canonical catalog) | src/Workflow/StellaOps.Workflow.WebService/Authorization/WorkflowPolicies.cs, src/Workflow/StellaOps.Workflow.WebService/Authorization/WorkflowEnvelopeAuthenticationHandler.cs |
| Built-in definitions deployed on startup | src/Workflow/StellaOps.Workflow.WebService/Bootstrap/WorkflowDefinitionBootstrap.cs, src/Workflow/StellaOps.Workflow.WebService/Definitions/*.json |
| Migration chain (001 baseline, 002 retention classes) | src/Workflow/__Libraries/StellaOps.Workflow.Persistence/Migrations/ |
| Fixed schema and table names | src/Workflow/__Libraries/StellaOps.Workflow.DataStore.PostgreSQL/WorkflowTables.cs |
| Connection key, fail-closed resolution, startup migrations, store bindings | src/Workflow/__Libraries/StellaOps.Workflow.DataStore.PostgreSQL/PostgresWorkflowDataStoreExtensions.cs, src/Workflow/__Libraries/StellaOps.Workflow.DataStore.PostgreSQL/PostgresWorkflowBackendOptions.cs |
| Retention options and hosted job | src/Workflow/__Libraries/StellaOps.Workflow.Engine/Services/WorkflowRetentionOptions.cs, src/Workflow/__Libraries/StellaOps.Workflow.Engine/Services/WorkflowRetentionHostedJobOptions.cs |
Doctor check doctor.workflow.retention.overdue-purge | src/Workflow/__Libraries/StellaOps.Workflow.DataStore.PostgreSQL/WorkflowRetentionBacklogCheck.cs |
| Backend, signal driver, rendering sections | src/Workflow/__Libraries/StellaOps.Workflow.Abstractions/WorkflowBackendOptions.cs, WorkflowSignalDriverOptions.cs, WorkflowRenderingAbstractions.cs |
| Oracle backend connection keys | src/Workflow/__Libraries/StellaOps.Workflow.DataStore.Oracle/OracleWorkflowDataStoreExtensions.cs, src/Workflow/__Libraries/StellaOps.Workflow.DataStore.Oracle/WorkflowOracleConnectionString.cs, src/Workflow/__Libraries/StellaOps.Workflow.Signaling.OracleAq/OracleManagedAqTransport.cs |
Signal pump retry budget, WorkflowAq options | src/Workflow/__Libraries/StellaOps.Workflow.Engine/Engine/Hosting/WorkflowAqOptions.cs, src/Workflow/__Libraries/StellaOps.Workflow.Signaling.OracleAq/WorkflowAqOptions.cs |
| Renderer providers and CI lanes | src/Workflow/__Libraries/StellaOps.Workflow.Renderer.ElkSharp/, src/Workflow/__Libraries/StellaOps.Workflow.Renderer.ElkJs/, src/Workflow/__Libraries/StellaOps.Workflow.Renderer.Msagl/; .gitea/workflows/workflow-renderer.yml, .gitea/workflows/workflow-oracle-tests.yml |
| Tests backing the persistence contract | src/Workflow/__Tests/StellaOps.Workflow.DataStore.PostgreSQL.Tests/WorkflowMigrationStartupTests.cs, src/Workflow/__Tests/StellaOps.Workflow.DataStore.PostgreSQL.Tests/WorkflowRetentionClassificationTests.cs, src/Workflow/__Tests/StellaOps.Workflow.WebService.Tests/WorkflowConnectionStringFailClosedTests.cs |
