Repository navigation
Releases: startr-trade/sutra
Release list
v0.2.0-rc.2
Second release candidate of the Rust-native engine — 148 commits since v0.2.0-rc.1.
The theme is making declarations mean something at runtime. Several things a package
could declare were checked when you sealed it and then quietly ignored by a running
engine. The largest of those is fixed here, together with the class of
silent-success failures that let it hide for so long.
Still a release candidate: APIs may move before 0.2.0. This one carries breaking
changes — see Upgrading at the end.
Files as first-class input
A CSV or fixed-width upload is now a typed message, not a bag of strings. Point a
channel at a schema codec whose manifest declares the format, and the codec validates
every row and every cell against your XSD in one decode — before the process starts:
# schemas/cdr/codec-manifest.yaml
schemaKind: xsd
formats: [csv, fixed-width]
csv:
delimiter: ","
header: true
fixed-width:
fields:
- {name: recordId, width: 12}
- {name: msisdn, width: 16}
- {name: durationSec, width: 6}- Both wire forms, one schema. Their content types are disjoint, so an upload selects
its parser unambiguously — one channel serves a CSV feed and a fixed-width feed, and
nothing downstream knows which arrived. - A bad cell names its record:
value[3].durationSec, not a byte offset into
something the engine synthesised. - The XSD's leaf types reach the cells — an
xs:intcolumn arrives as a number. - An empty cell is absence for an element declared
minOccurs="0", which is what
makes optional columns usable at all. fixed-widthreturns, with the manifest layout block it previously lacked. Because
that layout is configuration, its columns are checked against your type at package
time — a typo failssutra lint, not every row in production.
A full walkthrough ships as examples/call-log-load and as a new
worked example in the book.
Declarations that now bind
- A codec manifest is honoured. The engine hardcoded
["xml","json","yaml"]and never
openedcodec-manifest.yaml, so everyformats:declaration was inert once deployed: a
package sealed cleanly, linted clean, and then refused every upload. Lint and the engine
now build codecs through one entry point, and a manifest fault is a package-time error. - Payloads are validated against a package's schema at runtime — a schema in a package
previously meant nothing after deploy. - Validation fails closed. An intake with a validation contract and no
<q:onValidation>now refuses a failing payload instead of passing it to the flow. - Loops fail closed too.
<bpmn:loopDataInputRef>is a FEEL expression now, so a loop
can iteratepayload.valuein place — and one that resolves to nothing fails rather
than iterating zero times and reporting success.
Tooling
- One
generateverb replacesdocgen/catalog/schemagen, each keeping its
--checkdrift gate. Breaking. sutra codecs— what this binary can actually resolve.sutra create ciscaffolds the pipeline;create appscaffoldsdeploy/.gitignore.- The artifact catalog is a buildable mdBook, with BPMN diagrams auto-laid-out as SVG —
no BPMNDI needed in your files. - A compile-enforced BPMN element-support matrix, generated from the loader itself, so
the supported-element list cannot drift from the code. - One-line install and
sutra self-update. generate catalog --cleanremoves catalog pages whose source file no longer exists.
Errors that speak your format
RFC 7807 keeps its model; how it is serialised now follows the inbound content-type —
JSON (default), XML, YAML, or CSV. A client that posts a 40,000-row CSV and gets three bad
cells back receives a table it can diff against the file it sent, rather than JSON to
re-parse hunting for row 4,217.
Fixed
Three defects in the batch-load path, found by running the shipped example rather than testing
it. Its end-to-end test booted the engine with no datasource, so every valid upload stopped at
the persistence check — the codec half was covered thoroughly and the load half was structurally
unreachable. A tier-2 suite now boots the same archive against a real database and asserts the
receipt, the transformed rows, and that re-uploading a batch converges.
- A
<bpmn:dataObject>declared on a process was invisible inside its sub-processes, so a store
write in a multi-instance loop wrotenullfor every item. BPMN scopes a data object to its
container and everything nested in it; each container was being indexed in isolation. ack-mode: on-persistthrew away a<q:reply continue="true">receipt. The ack mode settles
when the caller is answered, not what with — an HTTP channel now answers202 Accepted
carrying the render, and a bare202only when the process replied nothing. Answering
immediately, with a document, while the load runs detached is now a supported combination
instead of a silent contradiction.- Intake rejections that were the caller's fault answered
500. The status map keyed on a literal
REJECTED.segment thatSUTRA.INBOUND.VALIDATION_REJECTdoes not carry, so a malformed batch
told the sender the engine had broken and the same bytes were worth retrying. Caller faults are
400now, with409/413/429/503for conflict, size, quota and capacity; anything unmapped
keeps500.
The catalog drift gate could not see a stranded page. generate catalog --check reported
"in sync" while a page whose source had been renamed sat in the tree — the writer only visits
pages it produces and the check looped that same set, so neither could see one whose source had
vanished. --check now reports those too, which turned up 42 genuinely stranded pages left
behind by module extractions and file moves.
Beyond that: check_output_conformance checked a <q:send>'s render against the
intake codec instead of its destination; multi-instance loop variables were invisible to
lint (a mandatory declaration produced a false "never initialised"); the @transient
read-after-wait gate was blind to respond-and-continue parks; the xml projection dropped
schema-instance attributes by namespace.
Security: h2 0.4.19 (RUSTSEC-2026-0258), chacha20 0.10.2 (0.10.1 was yanked),
jsonwebtoken 10, the container-scan backlog cleared, and the triage record time-boxed so
suppressions expire instead of accumulating.
Known gaps (honesty section)
A multi-instance loop is one durable step: a crash part-way through replays it from the
first item rather than resuming. That converges when every per-record effect converges — a
keyed upsert does, a <q:send> or a counter does not — and <q:process idempotent="true"/>
is how you assert it. Bounding the replay for large batches means delivering them in chunks.
No operations console UI. No published performance numbers.
Upgrading from v0.2.0-rc.1
- CLI:
sutra docgen|catalog|schemagen→sutra generate docs|catalog|schema-handler.
Check any Makefile or CI step that calls the old verbs. - Validation posture: run
sutra lint. Every affected intake is reported as
SUTRA.CONFIG.VALIDATION.POSTURE_UNDECLARED, naming both choices — declare
<q:onValidation mode="route"/>to keep the previous pass-through, ormode="reject"to
state the new behaviour. Schema-less ingress is unaffected. - Error-body parsing: a non-JSON caller now gets a non-JSON problem document. Callers
postingapplication/json, or no content-type, are unchanged. - Loops: a loop over a collection that may legitimately be absent must assign an
explicit empty list.
Getting started
- Book: https://sutra.startr.trade — start with Getting started.
- Build from source:
make build(Rust stable); container image viamake image.
Licensed under MIT or Apache 2.0, at your option.
v0.2.0-rc.1
The first public release of Sutra — a Rust-native, message-native workflow engine
built on BPMN 2.0 and DMN. One statically-compiled binary, one PostgreSQL, and your
processes as diagrams your reviewers can actually read.
This is a release candidate: the engine is feature-complete for the surfaces below and
gated by a three-tier test ladder (workspace suites, containerized conformance,
Kubernetes integration), but APIs may still move before 0.2.0. Feedback and issues are
very welcome — this release exists to be argued with.
The engine
- BPMN 2.0 as the programming model — events, gateways, boundary timers, message
correlation, human decision points; the diagram's semantics are the runtime's. - DMN + FEEL decisioning — decision tables and expressions validated against the
OMG DMN TCK: 3,300+ conformance tests run green in our gates. - Message-native by design — a start event binds a channel and a message type;
the engine decodes the real wire format, validates it against its schema at the door,
and drives the process with typed data. Violations are routable soft errors, not
exceptions. - Transports in the box — HTTP plus Kafka, RabbitMQ, AMQP 1.0, SQS, GCP Pub/Sub,
Dapr, Knative, and file channels, all behind one transport SPI; polyglot workers can
also pull work through the external-task API (fetch-and-lock, budgets, lock
extension).
Durable execution
- Typed snapshots at quiescent points — no history replay, no determinism
constraints on your logic, no history ceiling by construction. - Durable timers and schedules — durations, dates, ISO-8601 cycles, and timer start
events as deployment schedules. - Per-task retry policies (
<q:retry>) as durable timer parks, with a delivery
attempt ceiling and dead-letter capture, inspection, and replay. - Queryable history — terminal instances retained on your policy; a per-instance
audit journal survives completion. - Failure is a durable state — failed instances persist, can be inspected,
migrated, and resumed.
Deployment and versioning
- Sealed
.sutraarchives — content-addressed deployment identity; what you review
is what runs. - Hot deploy with a two-phase flip — zero dropped requests across a version swap;
in-flight instances stay pinned to their exact version. - An explicit migration API — single, batch, and cross-process migration with
validation and a dry-run mode; never a silent behavior change under live work.
Operations
- One binary + PostgreSQL — the whole topology. Your Postgres story (backups, HA)
is your durability story. - Execution lanes — in-process concurrency is a config key (
sutra.engine.shards),
not a cluster decision; replicas coordinate through database-backed ownership claims. - Honest health probes — readiness and liveness report real internals, including
dead execution lanes, so orchestrators restart exactly when they should. - Multi-tenancy that reaches the database — per-tenant channels and quotas,
PostgreSQL row-level security, and tenant-wide erasure that honors in-flight work. - OpenTelemetry throughout; container image and Kubernetes deployment modules in
the repo.
Verification and testing
- Deploy-time verification — schema-aware lint (every navigation a template or
expression makes must exist in the message contract), declared coverage for
compliance-critical paths, fail-closed archive validation. - A time-skipping test runtime — inject a virtual clock in tests (unreachable from
production configuration), fast-forward through a 30-day timer in milliseconds, on
the real execution path;sutra test simulatedrives the same seam from the CLI.
Tooling
- The
sutraCLI — package, lint, deploy, simulate, coverage, audit-replay, and
the admin surface. - A VS Code extension and a bpmn-js modeler plugin for the
q:extension
vocabulary, undertools/.
Known gaps (honesty section)
No operations console UI (the HTTP admin API is complete; a console is roadmap). Worker
helper libraries beyond the HTTP pull API are ecosystem work that hasn't happened yet.
No published performance numbers — we don't publish benchmarks we can't stand behind on
controlled hardware.
Getting started
- Book: https://sutra.startr.trade — start with Getting started.
- Build from source:
make build(Rust stable); container image viamake image.
Licensed under MIT or Apache 2.0, at your option.