VOR Stream v26.1.4 is the fourth patch release on the v26.1 line, and it concentrates on correctness and scale. A process that reads a file, table or S3 object into a queue delivers every row it read to the consuming node. The Waterways outputs download serves the run you selected. Four screens load in a fixed amount of database work regardless of how much data sits behind them, so a playpen that has accumulated years of studies and uploads opens as readily as a new one. The formula language gains a reference page, a beginner tutorial and four worked use cases, and the two aggregate functions that treated missing values differently from the rest now follow the same convention. A broad set of security updates spans the engine runtime, the web application, the Python dependency tree and the components the installer bundles.

Highlights

Every row that a process reads reaches the node that consumes it

Input nodes are the entry point of a process: a CSV file, a database table, a SAS dataset or an S3 object read into a queue for the rest of the process to work on. Rows travel through the queue in batches, and the reader signals that it has finished once the last batch is out.

This release tightens the ordering between those two events. The reader closes its queue channel before it signals completion, which is a synchronous handshake with the message broker, so the final batch is committed to the queue first and the consuming node reads the whole input, down to its last partial batch. All four reader types share the change, and a test inspects the generated source of each one to keep them in step.

Getting Started

See Input and Output Nodes for the reader node types and Queues for how rows move between nodes.

Waterways serves the run you selected

Process outputs are written to a directory named for the run, so re-running a process under a name that has been used before replaces what was there. On the Waterways screen, selecting an older run with a reused name and opening View & Download Outputs served the newer run’s data, in both the preview and the file download.

VOR Stream identifies which run is the current holder of each output directory, comparing names the same way the engine does when it reuses a run address. On a run whose outputs have been replaced, the download action is marked unavailable and says why: “Outputs only available for the most recent run of each name.” The action stays reachable with a keyboard and a screen reader, and activating it repeats the explanation rather than opening an empty view. Download Run Attachments is unaffected, because attachments are stored per run rather than per name.

The same treatment applies everywhere that jobs table appears: Waterways, the Report History dialog, and the Waterways panel inside the model editor.

A reference for the formula language, and a path into it

Formulas appear in two places in VOR Stream: risk factor transformations and the local transformations inside a structured model. Until this release, the documentation for them was spread across three surfaces that disagreed with each other, and the fullest version lived somewhere a modeler would not look.

There is now a single Formula Language reference covering both evaluation contexts: the operators, the function catalog by category, the rules for missing values, name resolution, and the warnings that nothing in the product enforces for you. Alongside it, Build Your First Structured Model walks through a complete model end to end, and Use Cases holds four goal-oriented guides that start from what a modeler is trying to express rather than from which function does what. The screenshots on both pages are generated from the models the pages describe, so every value on screen is the value the page tells you to enter.

Three worked examples in the old guide described models the product cannot build, and are rebuilt in the supported direction: a haircut written as a transformation that reads a lookup (expressions are evaluated before any lookup resolves), a category table mapping each category to a numeric constant (a lookup target is always a field, so it becomes one conditional chain), and a stress model given only a local transformation formula (a local transformation is only ever an input, so followed literally the run reports success and writes nothing).

The reference also corrects statements a modeler could act on and be blocked by. There is no if(condition, then, else) function, and there never can be: the expression engine reserves if. Conditionals are written with the ternary operator, which the old guide never mentioned. pow() is power(). sign(x) returns -1 or 1 and never 0, so a sign({x}) == 0 branch never fires. The comparison operators != and !(...) take the true branch against a missing value, where the other comparisons take the false branch.

Getting Started

Start with Structured Models for the concept, then the tutorial. For risk factor transformations, see Risk Factor Transformations and Data Sources.

mean and median skip missing values

VOR Stream treats a missing value the way SQL treats NULL: an aggregate skips it and returns a missing value only when every input is missing. sum, min and max have always worked that way. mean and median did not.

Given the values 10, missing and 20, mean returned nothing and median returned 10, which is wrong under any reading. Both now return 15. Both are also available in the formula editor’s Aggregation category, where they were previously absent, so a formula using them can be written and saved from the editor rather than only submitted through the API.

Both accept a list of values directly, mean({A}, {B}, {C}), and an array argument, mean([{A}, {B}]), so formulas written either way continue to work.

Date arithmetic in a formula

dateadd() shifts a date by a number of years, quarters, months, weeks, days or weekdays, and its result can be passed to datediff() and getvaldate(). In earlier releases the function was offered in the palette with a click-to-insert template and could never return a value, so a formula built from that template failed at run time.

getdate() is no longer offered. It reads the wall clock, which means a study using it would not return the same figures when re-run later, and inside a study the date a model wants is the study’s own. Typing it by hand reports a clear message naming the reason and pointing at the horizon date reference instead. The reasoning is recorded as an architecture decision record in the repository.

The dateadd, datediff and getvaldate templates also no longer insert date literals, which the formula validator rejects, so each template now produces a formula that saves.

Getting Started

The date functions and their accepted units are listed under Date in the Formula Language reference.

Stability & Quality Improvements

The Run Study screen opens on playpens with many studies. The study picker fetches what it renders. It previously retrieved each study’s entire methodology, down to every model’s script and uploaded workbook, for every study in the list, which on a playpen with a substantial history meant hundreds of database round trips. The list now costs a fixed three queries whether it returns two studies or twelve, so a long study history no longer needs archiving to keep the screen responsive. Opening a single study still returns its full methodology.

Run lookups from inside a process return promptly. The run detail call that Python and Go SDK code makes from a node previously issued several queries per scenario in the run’s study, so its cost grew with the size of the scenario set. The scenario data is loaded in one batch instead. On a benchmark of 150 scenarios the call went from 912 queries to 10, with an unchanged response.

The Data Management upload list loads regardless of how many files a playpen holds. The list retrieved the full contents of every uploaded file to render a table that shows none of it, which on a playpen holding large workbooks put the request over its memory budget. The list now reads only the columns it displays, and the archive and approve actions work through their selections in bounded batches inside a single transaction, so neither can produce a partial result.

History pages handle records with no author. Records written outside a browser session (through the SDK, a management command or a bulk operation) carry no user. The upload, study and filter history pages display those rows with a blank author, and keep them selectable in the archive tab’s “Archived By” filter. All three also read their related records in a single query rather than one per row.

Go model builds leave nothing behind. A run that uses Go models compiles each one in a temporary directory under the playpen source folder. Those directories are owned by the service account, so a playpen owner could not tidy up any that outlived their run. Build directories are now registered with the supervisor before they are created and removed when the job ends, whichever way it ends, and a cleanup that cannot complete while the process is alive no longer fails the build. Verified on a network-mounted deployment against production processes, across both completed and cancelled runs.

Node startup tolerates a transient network hiccup. The health check that confirms the SDK API is reachable now allows time for a dropped network packet to be retransmitted, so a momentary blip no longer takes a healthy service out of the pool while a node is starting. Continuous integration has recorded no occurrences since the change.

Formula help matches the engine. The editor’s help panel and the guide list exactly the functions a formula can call, so a function offered by the editor is a function that runs.

Administration & Deployment

RabbitMQ file descriptor limits. The message broker previously inherited the operating system’s default limit of 1024 open files, which is below what a sustained workload needs. Deployment raises the open file limit for the VOR service user to 65536, the value RabbitMQ’s production checklist recommends, configurable through the new vor_user_max_open_files variable. Consul and the other supervised services share the same limit.

Restart Required

Services read this limit from the supervisor when it starts, so a host where the supervisor is already running needs a restart or a reboot before the new limit reaches the broker. The System Requirements page has a File Descriptor Limits section covering how to verify the effective limit against the running broker process.

Installer components updated for security. The bundled third-party components move to their latest patch releases within their existing series: Erlang/OTP 27.3.4.16 (six advisories, including a denial of service in the Erlang port mapper that every broker node runs), PostgreSQL 14.24 (the August security batch, headlined by a flaw letting a replication user select any loadable library as a logical decoding output plugin), Django 5.2.17, and RabbitMQ 4.2.9. Every checksum was verified against the artifact the build fetches.

For implementers

PostgreSQL is compiled only when no PostgreSQL binary is present, so a host already running 14.23 stays on 14.23 and fresh installations get 14.24. Erlang is installed before RabbitMQ is stopped, so on a host that already runs both, this upgrade replaces the runtime under a live broker. Both are pre-existing behaviors, but this is the first release in a while that moves Erlang and RabbitMQ together, so validating on an upgraded host rather than only a fresh install is worthwhile.

Security

This release applies a broad set of security updates:

  • The engine runtime moves to Go 1.26.6, closing eight standard library advisories across TLS, HTTP, XML, ASN.1, URL parsing and template escaping.
  • The Angular runtime moves to 21.2.19, closing a cross-site scripting issue in internationalized templates and a transfer-cache key ambiguity.
  • The deployment toolchain closes a high-severity argument-injection flaw in the Ansible role installer, a path not used by any VOR Stream playbook.
  • Updates land across the Python dependency tree, including the cryptography library, the SQL parser Django depends on, and the package installer the deployment bundles.
  • The etcd client library the engine’s configuration loader links moves to its patched release.

Every finding is verified against the project’s dependency scanners before release.

Upgrade Notes

Drop-in Upgrade for the Running Services

v26.1.4 upgrades in place from v26.1.3 with no breaking changes to the engine or the process language. One database migration is applied as part of the upgrade.

Restart the supervisor on Ansible-deployed hosts so the raised file descriptor limit reaches the running services.

A few responses have changed shape for anything reading the REST API directly. The study list with detail=True carries the fields the Run Study screen renders and no longer nests each study’s methodology; the single-study call still returns the full methodology tree. Factor history is ordered by factor name within a date bucket, which makes the factor picker alphabetical rather than arbitrary. The upload type list’s order is now the database planner’s; it was unspecified before as well.

Processes with a file, database, SAS or S3 input node deliver their full input on this release, with no process or model change required.

Saved formulas calling getdate() report a clear message naming the reason. Formulas using dateadd() evaluate and can be composed with datediff() and getvaldate(), and the templates for the date functions produce formulas that save.