Skip to content

JMA

2.2.0

API version: 10.1.0

Added

  • UploadWorkData can file the new WorkData into a folder, tag it and mark it secret, all in the commit that creates it (issue #179). Before, each took a follow-up call (MoveToFolder, SetTags, MarkAsSecret), and a client failing between them left the WorkData in the root, untagged or not secret. The properties go in an optional form field UploadWorkDataParameters holding {"Folder": <folder URL>, "Tags": [...], "Secret": true} (every member optional), before or after the file. Sent before the file, an unknown or foreign folder or an invalid tag fails the upload before any content is stored; sent after it, the stored content is removed again. The upload still streams the file without buffering it. The SIREN Upload action of the WorkData root lists the new field (class FileUploadActionWithParameter) and keeps the class FileUploadAction, so existing clients continue to work. Without the field an upload behaves as before.

Fixed

  • Setting a WorkData’s tags with a null tag ({"Tags":[null]}) failed with HTTP 500 instead of a validation error (400).

2.1.1

API version: 10.0.0

Fixed

  • Saving a job that has outputs could fail as “resource already exists” when another save of the same job had just committed, like the tag conflict fixed in 2.1.0: every save also replaces the job’s output usage rows, and the stale save collided with the new ones before its version check. When a worker set a job’s tags while the job-update listener saved the job’s start, either the worker’s request failed with 423 (execute_workflow failed, issue #184) or the listener dropped the start, leaving a completed job without its start time and worker. It is now retried like any other concurrent modification. Present since 2.0.0.

2.1.0

API version: 10.0.0

Changed

  • Job updates from workers are consumed from the new single-active-consumer queue Job.All.Updates.SingleActive, one message at a time, so the updates of a job are applied in the order the worker sent them however many instances run. With competing consumers on Job.All.Updates, two instances could apply a job’s “finished” before its error (the failed job ended up completed) or before its result. When a listener stops it finishes the message in processing before a standby takes over. Workers are unaffected: routing keys and message contracts are unchanged. Delivery stays at least once: after a redelivery or a failover an output’s metadata comment can appear twice; its name and tags are unaffected. With more than one instance, an output that receives more than one metadata update can be left with the older one after a failover; a step that writes each output once sends one (README, “Job update messages”; issue #182).
  • Operator-facing: at startup JMA unbinds Job.All.Updates and drains what is left in it before consuming the new queue. The drain acknowledges what it applied only once the queue is empty: an interrupted drain fails the startup and leaves everything it had applied in Job.All.Updates, and the restarted pod applies it again, in order. An update published in the instant of the switch reaches both queues; its copy is skipped by message id, and never applied after a newer update of its job (an update applied a second time appends its output metadata comment again, like any redelivery). Roll this release out with a single instance, scale out afterwards. Once Job.All.Updates is empty it can be deleted together with the job-all-updates-dead-letter broker policy. Rolling back to an earlier release needs a controlled hand-back of the queue (README, “Rolling back to a release without the single-active queue”): deleting Job.All.Updates.SingleActive without it loses the updates only it holds.

Fixed

  • A job update that is valid JSON but no usable message (null, or without header or content) is dead-lettered instead of staying unacknowledged. The same holds for any message whose handler fails unexpectedly, in every listener; with the job-update listener taking one message at a time, such a message would otherwise have stopped all job updates.
  • A job update whose command found the job store temporarily unavailable (a database timeout, lost connection or failover) was dead-lettered at once, and the job’s later updates were applied without it: a failed job’s “finished” without its error left the job completed. It is now retried in place for about three and a half minutes, which holds the job updates behind it until the store is back; only a longer outage dead-letters it.
  • Saving a job that has tags could fail as “resource already exists” when another save of the same job had just committed (every save replaces the job’s tag rows, and the stale save collided with the new ones before its version check). The job-update listener took that for an update already applied and dropped it, and API requests failed. It is now retried like any other concurrent modification.
  • With several instances, an instance whose broker connection recovered re-created the command queues of jobs another instance had already finished (the RabbitMQ client’s automatic recovery re-declares every queue a connection declared and did not delete itself). Job command queues are left out of that recovery; they are durable, so those of running jobs survive a reconnect anyway.

2.0.1

API version: 10.0.0

Fixed

  • Optional input data slots (minSlots=0) can be filled. An empty optional slot counted as configured from the start, so it never offered SelectWorkData, SelectWorkDataCollection or SetFolderSource, and clients failed with “action is not available”. The slot now offers these actions while it is empty or short of its minimum, and only while the job can still be edited.

2.0.0

API version: 10.0.0

Releases as 2.0.0 (MAJOR). Two of the three MAJOR criteria in README “Choosing the version” apply: the REST contract breaks (the Admin/RestoreJobReadModels endpoint is removed and the input SetFolderSource action takes FolderUrl instead of a Folder path — hence the ApiVersion major step to 10.0.0), and the database change needs manual intervention when upgrading (the event-store cutover is irreversible, drops columns the previous release still reads, installs a legacy-write fence, and requires a maintenance window). RabbitMQ message contracts are unchanged.

Added

  • Action templates can source a collection input data slot from a folder, the same way jobs can (SetFolderSource action on a template input data slot, recursive by default; mutually exclusive with item-by-item selection). The folder is stored as a snapshot path; jobs created from the template — including those started by a trigger — resolve and materialize the folder’s current contents at their own execution time, so a triggered template becomes an “inbox” that processes whatever is in the folder on each run.
  • CreateJob accepts an optional Folder to create the job directly inside a folder (omit for the root). The target folder must exist for the owner, mirroring the MoveToFolder action.
  • Output data slots can be directed into a folder (SetOutputFolder action on a job’s output data slot). The slot stores the folder as a snapshot path; the WorkData produced for that slot is filed into the folder (created if missing) when the output placeholders are built at execution time. An empty or root path clears the target, writing the output to the owner’s root (the default, unchanged behaviour).

Changed

  • Job and template input SetFolderSource actions accept FolderUrl instead of a Folder path. Send an existing folder’s resource URL, or explicit null for the owner’s root. The selected folder must belong to the job/template owner; its current path remains the stored snapshot. Output folder destinations still accept paths and can be created at execution time.
  • Jobs are no longer event-sourced. The event-sourced JobAggregate and its read-model projection are replaced by a single plain Job domain entity persisted through EF Core via JobDto/IJobRepository. Writes now happen in a single transaction guarded by an EntityVersion optimistic-concurrency token (no Marten event store, no dual write, no read-model repair jobs). Job REST payloads and action availability are unchanged (availability is computed live from the domain Job instead of persisted *OpAvailable columns).

Removed

  • The transitive Marten event-store dependency, through Foundation 3. Legacy migration tests use captured PostgreSQL schema and event fixtures, so the test projects do not require Marten either.
  • The Admin/RestoreJobReadModels action and endpoint (route Admin_RestoreJobReadModels). Job read models are no longer a separate projection that can lag, so there is nothing to rebuild.
  • Operator-facing: the second Postgres connection pool PostgresEventStoreDb and the JMA_EventStore health check (the event store shared the main database and is gone).
  • The CopyToOrg and CopyToUser actions on a ProcessingStep, with their endpoints (routes ProcessingStep_CopyPsFromUserToOrg and ProcessingStep_CopyPsFromOrgToUser). Copying a ProcessingStep between a user and an organization never produced a usable step: the copy is given the destination owner, but job offers are routed on the owner (Job.<OwnerId>.<FunctionName>.<Version>.JobOffer) and the copy is created undeployed, so no worker was ever bound to the copy’s routing key and jobs selecting it were offered to a queue nobody consumed. Register the step under the destination owner instead (upload it with that owner’s API key), which binds a worker to the correct key. Copying WorkData between a user and an organization is unaffected and remains available.

Fixed

  • Invalid folder paths retain their detailed validation messages instead of returning only “Validation of command parameters failed.”
  • Upgrading from event-sourced jobs now validates and backfills legacy folder-input snapshots within the original cutover migration before destructive schema changes, preserving unresolved, resolved-empty and ordered populated inputs. Inconsistent history aborts the cutover transaction, and the existing startup migration check prevents serving with pending migrations. A database guard rejects legacy job-event appends during pod replacement.
  • Disallowing output data deletion now notifies job subscribers, so the Allow/Disallow actions stop showing a stale state until the next unrelated job change.
  • Resolving a folder-sourced input while a job is being scheduled no longer lets it start on a set that scheduling never prepared. Scheduling now aborts if a slot’s execution snapshot or folder source changed after preparation, leaving the job ready to be scheduled again.
  • Scheduling a job on behalf of another owner no longer fails when one of its output slots targets a folder. The output placeholder is now created for the job’s owner, matching the folder it is filed into; the scheduling user is still recorded as its creator.
  • Scheduling access denials and infrastructure failures no longer change jobs to Error. Folder-input validation failures recheck access, readiness, and the current inputs before failing a job.
  • Scheduling holds the owner’s folder-tree lock while resolving output destinations and committing folders, placeholders, and job references together, preserving the hierarchy during concurrent tree changes.
  • Retrying scheduling after an output-folder edit moves existing placeholders to the current destination without replacing their files or tags. A target change between preparation and scheduling aborts dispatch until the job is prepared again.
  • Reading jobs with large input/output collections no longer multiplies those collections into a single SQL result. Job reads and folder pages load their graphs in a consistent database snapshot, including across transient read retries.
  • Clearing or refreshing a template’s pinned folder inputs removes obsolete usage rows so released files can be deleted. Unchanged selections retain their usage rows, including in other template input slots.
  • Setting a template input’s folder source with an invalid or missing slot index returns HTTP 400 with validation details and preserves the template’s existing configuration and input references.
  • Job input Resolve/ClearResolved and output SetOutputFolder actions are only advertised when the job is editable and the caller has execution permission. Template SetFolderSource/Resolve/ClearResolved actions also check their respective command permissions, following the existing owner/admin rules.
  • A worker status, result, error or output-metadata message that arrives after its job was deleted is acknowledged as a late delivery instead of being dead-lettered into Job.All.Updates.Rejected. Deleting a job removes its row, so such a message can never be applied and the rejected queue only accumulated messages nothing could act on; the skip is logged with the job id, and the worker’s access token is revoked as for any other settled job. Operators should expect fewer messages in Job.All.Updates.Rejected — the remaining ones are genuine failures.

1.0.1

API version: 9.10.0

Fixed

  • Reading historical event streams no longer fails after a change in assembly version. The computed Event.EventType was persisted into every event — its [JsonIgnore] was System.Text.Json’s, which the Newtonsoft-based Marten event-store serializer does not honour — as an assembly-version-qualified type name, and deserialising it broke whenever the running assembly version differed from the one that wrote the event (as happened on the switch to semantic versioning). The property is now also ignored by Newtonsoft (Foundation.Util 2.2026.902.225), so old events deserialise regardless of assembly version.

1.0.0

API version: 9.10.0

Added

  • Multi-tenancy step 1: every request must carry the trusted X-Tenant-Id header. All resources are stamped with the tenant they were created under; processing step queries are scoped to the caller’s tenant. Data created before multi-tenancy belongs to the tenant pinexq.
  • Persistence setup modes OnStartup (default), MigrateAndExit and VerifyOnly (PersistenceSetup:Mode), so schema migration can run as a deploy-time job while runtime pods only verify.
  • Boot-time schema setup is serialised across replicas with a PostgreSQL advisory lock.
  • GET api/Info reports the BuildVersion as the semantic version of the build, derived from the git tag (e.g. 1.0.0+<commit sha>). Container images are tagged with the same version (v1.0.0), the latest tag is no longer published.
  • Server-Sent Event delivery works across multiple replicas: API events are fanned out over a RabbitMQ fanout exchange so a client connected to one replica receives events produced on another. Off by default (ApiEventBackplane:Enabled; exchange RabbitMq:Exchanges:ApiEvents, default api-events-fanout), and single-replica behaviour is unchanged. Delivery is scoped by owner and tenant, failing open when a producer has no tenant context (e.g. a message-driven job update) so events are never dropped.

Changed

  • Portal-api base URLs and job logs/metrics links are resolved per tenant.
  • Registering a processing step rejects parameter schemas with deeply chained $ref definitions.
  • The job-update listener retries commands that hit a version conflict and rejects messages it cannot apply instead of acknowledging them blindly; rejected messages dead-letter into the durable queue Job.All.Updates.Rejected for inspection and replay.

Fixed

  • Leaked RabbitMQ channels in the processing exchange.
  • Folder feature review findings (validation and navigation details).
  • Jobs no longer get stuck at Processing in the read model when the output cleanup of a completion races a concurrent update of the same job; the completion now starts from a clean unit of work.

20260820.333

API version: 9.10.0

  • Introduce Folders: Jobs, WorkData and ProcessingSteps can now be organized into a per-owner folder tree. A new Folder resource and a FolderRoot (which holds root-level, unfiled items) are reachable from the entry point.
  • Folders can be created, renamed and moved. Folder names must be unique within their parent folder, and moving a folder into its own subtree is rejected.
  • A folder exposes paginated Children (its subfolders) and Contents (the Jobs, WorkData and ProcessingSteps filed in it, ordered by date) as sub-resources.
  • Jobs, WorkData and ProcessingSteps can be filed into a folder and moved between folders. Their queries gain a folder filter, including an option to match a folder together with its whole subtree (recursive).
  • Each Job, WorkData and ProcessingStep now indicates its containing folder: a ParentFolder link (present only when the item is not at the root) and a FolderPath string property (the root is shown as /).
  • Sub-jobs can optionally inherit their parent job’s folder via InheritParentFolder.
  • Deleting a folder only succeeds when the folder is empty; it is refused if the folder still contains items or child folders (structural delete, no cascade).
  • Clearing a folder (DeleteAllInFolder) best-effort deletes every item in the folder and its descendants (the folders themselves are kept) and returns a per-object report listing each item as deleted or skipped, with a reason for any skip.
  • API version bump to 9.10.0

20260723.327

API version: 9.8.0

  • Jobs no longer remain in Processing when the worker executing them crashes or is evicted: the platform now automatically fails such jobs — they transition to Error with an explanatory error_description.
  • Admins can now filter job queries by WorkerId.
  • API version bump to 9.8.0

20260603.325

API version: 9.7.0

  • Cron Action Triggers now support a TimeZoneId; missing time zones default to UTC.
  • Added trigger activation windows with optional StartsAt and ExpiresAt values; expired triggers are deleted automatically, with optional deletion of associated templates.
  • Added TQL tag query support for Action Templates, matching the query behavior already available for Jobs, ProcessingSteps and WorkData.