Changelog
All notable changes to Boruna are documented here. Format follows Keep a Changelog. Versioning follows Semantic Versioning.
Unreleased
Added
- Instruction-count benchmarks (
benches/benches/instructions.rs, Gungraun under Valgrind) and a CI job that fails when a PR makes a benchmark execute more than 3% more instructions. The wall-clock bench comparison stays as an informational comment: on the shared runner identical code moved by 10-40% between runs. scripts/check-doc-examples.py: every complete.axexample in the published docs must compile; runs in the “Docs site” CI job.- Website and docs site generated from the repository’s Markdown (
site/,scripts/build-site.py): a landing page, the docs of the latest release and ofmaster, and a command reference generated from the binary. CI builds it and fails on a missing listed page or a broken internal link.
Fixed
- Language spec errata: records are declared with
type(notrecord), enum variants have no payload or one positional payload and are matched by bare name, and records are not destructured in patterns. The spec’s own examples now compile. - Docs corrected against the code and published on the site: framework spec and API, effects,
actors, orchestrator, packages, operations, governance, platform overview, determinism
contract, traces, threat model (Rekor anchoring exists since 3.2.0), roadmap, FAQ, security
model and compliance evidence. Three obsolete docs moved to
docs/archive/. - README and the landing page said workflow bundles record every capability call; they record
steps with output hashes, failures, approvals, triggers and confidence-gate decisions. The
gap is now listed in
docs/limitations.md. boruna-orch: the replay and diag gates rancargo run -p llmvm-cli, a package that does not exist (the crate isboruna-cli), so neither gate ever ran the CLI. Both now record the exit code and the end of stderr, so a failed run is visible in the gate report.- Broken links in the docs: compliance template paths in
docs/reference/compliance/README.md, CHANGELOG anchors indocs/lts.md, and CHANGELOG links to guides removed in v3.0.0 (now pointing at v2.0.0 on GitHub).
[3.5.0] — 2026-10-03
Install in one command on every supported platform, three match bugs fixed, and a language
specification that matches what the compiler accepts. Additive for programs that compiled
before, but see Fixed: programs that use integer patterns or nested match now behave
correctly, so their bytecode and module hashes change.
Added
install.sh(Linux, macOS) andinstall.ps1(Windows): one-line install of the release binaries. They pick the build for the OS and CPU, verify it againstSHA256SUMSand install nothing on a mismatch. Tested in CI on all five native platforms, including a tampered archive and Windows PowerShell 5.1.- Diagnostic
E010(warning): a binding declared withoutmut, a parameter or aforloop variable is reassigned. The compiler has accepted this sincelet mutwas introduced, because themutflag was parsed but never checked.boruna lang checkand the MCPboruna_checktool now report it, andboruna lang repairadds the missingmut. It stays a warning in language 1.x and becomes an error in language version 2.0.
Changed
- Language version is now
1.1. The specification (docs/spec/ax-language-1.0.md) now coverslet mut, assignment,whileandfor(§4.5), which the compiler has accepted since v2.0 while the spec still listed them as reserved words. No program that compiled before stops compiling. std-guardandstd-jsondeclare their loop counters withlet mut(found byE010).
Fixed
matchon aSome(x),Ok(x)orErr(x)value written in source never took theSome/Ok/Errarm (“no match found for value”); only values returned by builtins matched. The literals now build the same values builtins return, so they also compare equal to them.- Integer literal patterns (
match n { 3 => ... }) never matched; only_did. They now compile to equality checks, like string patterns. - A
matchinside an arm of anothermatchcould run the outer match against the inner arms and return a wrong result or fail. - A string or integer
matchwith no matching arm and no_arm returned()or failed later with “stack underflow”; it now fails with “no match found for value”, like other matches. - Because of these fixes, the bytecode of programs that use integer patterns or nested
matchchanges, and so do their module hashes. A step that returned aSome/Ok/Errliteral now records it asSome(..)/Ok(..)/Err(..)instead of an internal enum value. docs/limitations.mdsaid.axhas no mutable variables and no loops; it has both.
[3.4.0] — 2026-10-03
Platform release. Boruna now ships and is tested natively on macOS (Apple Silicon and Intel), Windows (x64 and Arm) and Linux (x86_64 and arm64). No change to workflows, workflow hashes or evidence bundles.
Added
--version/-Von all four binaries (boruna,boruna-mcp,boruna-pkg,boruna-orch).- Release binaries for macOS Intel (
x86_64-apple-darwin) and Windows (x86_64-pc-windows-msvc,aarch64-pc-windows-msvc, packaged as.zip). Previously the README listed macOS Intel but no such binary was published, and there was no Windows build. - CI job that builds, tests and runs an example workflow with
evidence verifynatively on macOS arm64, macOS Intel, Windows x64, Windows on Arm and Linux arm64. .gitattributespins LF line endings so golden files and the changelog are identical on Windows.
Fixed
- Windows:
borunaoverflowed the 1 MB main-thread stack on every command except--version. It now runs on a 64 MB stack thread on all platforms. - Windows:
.axsources with CRLF line endings failed to compile (unexpected character "\r"). - Windows: patch bundles with a rooted path such as
/etc/passwdpassed the absolute-path check, because such a path has no drive letter. Rooted, drive-letter and backslash-rooted paths are now rejected on every platform. - Workflow trigger tokens read
/dev/urandomand so could not be created on Windows; they now use the operating system’s random source and fail instead of falling back to weak randomness.
[3.3.0] — 2026-10-03
Additive feature release — no breaking changes. Two ideas from the agentlanguages.dev review: a calibrated way to let confident answers skip a human reviewer, and agent documentation that is generated from the binary so it cannot drift from the commands that exist. Existing workflows, workflow hashes and evidence bundles are unchanged.
Added
- Calibrated confidence for approval gates — borrowed from conformal prediction
(Quasar; the agentlanguages.dev review). An
approval_gatecan carry aconfidence_gate(source_step,calibration,alpha_permille). When the upstream step’sIntscore (permille) reaches the threshold computed from the calibration file, the gate completes without a human. Otherwise it pauses as before. The guarantee: a wrong answer is auto-approved with probability at mostalpha. Too few wrong calibration examples means the threshold isnever. The decision, score, threshold and the exact calibration file go into the evidence bundle (confidence_gates.json,confidence/<step>.calibration.json) and the hash-chained audit log;boruna evidence verifyrecomputes every decision, so a changed decision, a swapped calibration or a dropped file is rejected. Gate decisions cannot be redacted (evidence redactrefuses them andverifyrejects a redacted policy entry), because a redacted entry could hide one. The limits of verification are indocs/architecture-conformal-gating.md.evidence report --framework eu-ai-actlists auto-approved gates and marks Art. 14 as PARTIAL, since a gate that completed on a score is not human oversight.boruna confidence thresholdshows the threshold before wiring it up. Works in-process (sequential, concurrent waves, resume).--submit-onlyand the coordinator reject workflows that use it. Existing workflow hashes are unchanged. Seedocs/design-conformal-gating.mdandexamples/workflows/confidence_gated_review. - Agent docs that cannot drift — borrowed from Vow and Lume (agentlanguages.dev review).
boruna skills get clinow ends with a command reference generated from the installed binary. Half of the top-level commands (12 of 24) were absent from the hand-written text.boruna skills emit <dir>writes the skills asSKILL.mdfolders;boruna skills pack "<query>" --budget Nreturns only the relevant sections, deterministically. - A unit test fails when a hand-written skill names a
boruna <command>that does not exist.
Fixed
ax-languageskill pointed agents atboruna check, which does not exist. The command isboruna lang check.crates/llmvm/src/actor.rs:drain(..).collect()replaced withstd::mem::take(rust 1.98 clippydrain_collect, which-D warningsrejects). Behavior is unchanged.
[3.2.0] — 2026-07-18
Additive feature release — no breaking changes. Closes the two credibility gaps in
Boruna’s tamper-evidence story identified during the adjacent-market research:
external witnessing (so the record isn’t just “trust the recorder”) and privacy
(so a sealed bundle can carry redactable data). Together they compose — a redaction
preserves audit_log_hash, so an out-of-band anchor distinguishes an authorized
redaction from a tamper.
Added
- Transparency-log anchoring —
boruna evidence anchor <dir>submits the bundle’s attestation to a Sigstore Rekor log and stores the inclusion proof + signed entry timestamp back into the bundle, giving an external witness and a trusted timestamp.--rekor-urlpoints at a private Rekor for air-gapped use;--offlineemits the entry payload for out-of-band submission;--verifychecks a stored proof (RFC 6962 Merkle inclusion math) with no network. Network is opt-in, behind therekorcargo feature (ureq); the default build stays network-free. Keyless (Fulcio) signing is design-noted (orchestrator/docs/keyless-signing.md). - Verifiable redaction —
boruna evidence redact <dir> --event <i> [--field <f>]removes PII from a sealed bundle without breaking verification. The audit chain now commits to a per-entry content hash (entry_hash = SHA-256(seq ‖ prev ‖ content_sha256), bundle format1.1, back-compatible with1.0), so redacted content is replaced by its commitment and the chain still verifies.audit_log_hashis invariant under redaction but changes under tampering, so a redaction is an authorized, recorded transformation while a content edit is detected.evidence verifyreports which entries are redacted. Encrypted bundles must be decrypted first.
[3.1.0] — 2026-07-18
Additive feature release — no breaking changes. Deepens Boruna’s two moats:
verifiable/auditable evidence (standards interop, compliance reporting,
observability export, sealed contract/guard verdicts) and agent authoring
(exact-signature lookup, a run-and-seal execution cell, an agent corpus). Ideas
were mined from adjacent tooling (Temporal/LangGraph/Langfuse/Credo AI/SLSA/
in-toto/Sigstore) and from the agentlanguages.dev peer catalogue, then mapped
onto Boruna’s determinism + evidence model.
Added
- In-toto + DSSE attestation —
boruna evidence attest <dir>emits the bundle as an in-toto Statement (predicateType https://boruna.dev/runtime-provenance/v1) wrapped in a DSSE envelope signed with the bundle’s ed25519 key;--verifychecks it. Makes runtime-execution provenance consumable by the supply-chain ecosystem (cosign,in-toto-verify). Additive — the native bundle is unchanged. Predicate schema:docs/spec/runtime-provenance-predicate-1.0.md. - Compliance-mapping report —
boruna evidence report --framework eu-ai-act|nist|iso42001verifies a bundle, then maps its contents to the specific obligation each helps satisfy (EU AI Act Art. 12/19/26, NIST AI RMF, ISO/IEC 42001), honestly flagging gaps. A technical mapping, not a certificate of compliance. - OpenTelemetry export —
boruna evidence otel <dir>emits the run as OTLP/JSON spans (no SDK dep, no network) with tamper-evidence attributes (boruna.bundle_hash,audit_log_hash,signature.keyid) andgen_ai.*spans forllm.*calls, so a run surfaces in any OTel backend while linking back to a verifiable record. - Sealed contract + guard verdicts —
requires/ensurescontract checks now record aContractCheckevent (pass and fail) into the hash-chained evidence log. New__builtin_guard(value, passed, label)runs a deterministic output check, traps fail-closed on violation, and seals the verdict — so “the guardrail ran on this model output and returned this verdict” becomes a replayable, tamper-evident fact. std-guardstandard library (14th lib) — pure, deterministic output validators (length/range/allow-list/ban-list/refusal-heuristic/json-shape).- MCP tools (now 14) —
boruna_symbols(exact typed signatures for.axsource) andboruna_run_sealed(compile + run + replay-verify → a verifiable execution record). - Quickfix-coverage CI gate — every auto-fixable diagnostic must ship a repair strategy or be explicitly allow-listed.
- Agent corpus & docs —
llms.txt, an.axteaching primer, a static agent portal manifest, an evidence threat model, and a runtime-execution-provenance positioning doc.
Fixed
docs/reference/ax-language.mdsyntax drift — corrected to the real grammar (records usetype, enum variants are unit or single-payload, match arms use bare variant names), verified withboruna lang check.
[3.0.0] — 2026-07-18
Removes the entire HTTP / serving / distributed-execution layer. Boruna is now a local deterministic engine + CLI — no HTTP server. The compiler, VM, orchestrator engine, evidence bundles, deterministic replay, and every local CLI command are unchanged. Breaking, hence the major bump: public CLI commands and a build feature were removed.
Removed
- The HTTP serving / distributed-execution layer — the coordinator (distributed HTTP server), distributed workers, active-active HA, coordinator mTLS, the workflow dashboard, the evidence web viewer, and the approval console.
- The
servecargo feature and its server dependencies (axum, hyper, tower, reqwest, rustls, …). - CLI commands
coordinator,dashboard,worker, andevidence serve; and the--coordinator/--coord-tokenflags onworkflow run/approve/reject/trigger. Approval and trigger gates are still handled locally viaboruna workflow approve/reject/trigger+resume. - Net: ~11,000 lines removed.
Kept
- The local engine (
boruna-orchestrator,boruna-vm,boruna-compiler), evidence bundles, deterministic replay, capability-policy enforcement, and every local CLI command (run,workflow …,evidence verify/inspect,lang,template,migrate,framework,policy,metrics), plus the MCP server. - The
httpfeature — the VM’s outboundnet.fetchcapability for workflow steps (a workflow capability, not a server).
[2.0.0] — 2026-07-17
First major release. A security-hardening + language-completeness sprint that remediates every finding from a whole-codebase research audit (3 High, several Medium, plus the “statically typed but unchecked” language gaps). It carries deliberate breaking changes — integer overflow and several coordinator/framework defaults now fail closed — hence the major bump. See Breaking changes below; each has a documented migration or override.
Security
- SSRF hardened in the live HTTP handler. URL safety is now split into a syntactic check and a live DNS-resolution check that rejects every resolved private/loopback IP (IPv4 and IPv6, brackets stripped); redirects are followed through a bounded manual loop that re-validates each hop, so a public host can no longer redirect into the internal network.
- Coordinator cross-worker claim hijack closed (S6). Completing, failing, or
extending a step now requires the caller to own the claim; a non-owner is
rejected with
403 coord.claim_not_owned, checked at the trust boundary under the store lock. - Coordinator approval-gate forgery closed (S9). An approval gate now mints a
per-gate token;
approve/rejectrequire it (403 coord.approval_token_invalid). - Evidence bundles are now tamper-evident. Verification gained an external
anchor (
evidence verify --expected-bundle-hash <hex>) plus optional ed25519 manifest signing (--verify-key/--require-signature) and a--require-encryptiondowngrade guard — a forged but internally-consistent manifest that plain verify accepted is now caught. - Content-addressing enforced at the coordinator. A worker’s
output_hashis verified againstSHA-256(output_json)on completion. - XSS fixed in
evidence serve— all bundle-derived HTML is escaped. - Key material zeroized — evidence DEK/KEK wiped on drop.
- Path-traversal guards added to template names and storage
ref_to_run_id(S3/GCS/Azure). - Crafted-
SpawnActorDoS fixed — a bad function index fails the actor instead of panicking the VM.
Added
- User enum construction + real per-variant match tags. Enums could be
declared and matched but never constructed; there is now an expression form
Enum::Variant/Enum::Variant(payload)(new::token) that compiles toMakeEnum, and match arms dispatch on the variant’s real declaration index (they previously all collapsed to the first arm). - Higher-order / indirect calls via new
Op::CallIndirect— a function passed as a value now dispatches correctly (previously hardcoded to fn #0). forloops,Map<K,V>/Fn(..) -> Ttype annotations, andensurespostconditions.- Static arity checking — a direct call to a named function with the wrong argument count is now a compile error.
- Warn-only static type-consistency checking (E009 warnings).
lang check/boruna_checknow surfacelet-annotation and call-argument type mismatches as warnings, without blocking compilation — the first, non-breaking step of a staged rollout toward strict typing.
Breaking changes
- Integer overflow is now a runtime error (
VmError::ArithmeticOverflow), where it previously wrapped (release) or panicked (debug). - The coordinator refuses to start on a non-loopback bind without auth.
Override for trusted networks:
BORUNA_COORD_ALLOW_INSECURE=1. - The coordinator rejects an
output_hashthat doesn’t matchoutput_json(previously trusted). - Framework policy defaults fail closed: an empty or malformed
policies()now denies (was allow-all). Apps that define nopolicies()at all keep the allow-all convenience. - Codegen rejects element/field/argument counts above 255 with a compile
error instead of silently truncating a
u8operand.
Fixed
while-body trailing-expression stack leak (one operand leaked per iteration).- Documentation/version/count drift (README, CLAUDE.md, stability, stdlib manifest) corrected to the real workspace state.
[1.9.0] — 2026-07-15
Ninth feature minor on the 1.x LTS line. Fourth and final sprint of the agentlanguages.dev competitive-borrow program (Theme D-lite): capability-row inference — the compiler now infers each function’s minimal capability set and flags over-declarations.
Added
- Capability-row inference + over-declaration check (
boruna lang caps). Theme D-lite — borrowed from AILANG (effect-row inference: the compiler computes the minimal capability set and flags over-declaration). NewModule::needed_capabilities(func_idx)infers the capabilities a function actually needs — those it invokes directly viaCapCallplus everything its transitive callees need (cycle-safe, deterministic).Module::over_declared_capabilities(func_idx)returns the capabilities a function declares (!{...}) but never (transitively) uses — an over-grant of authority (a least-privilege smell; not a correctness bug, since the VM still gates at runtime). The newboruna lang caps <file.ax> [--json]command reports each function’s declared vs. inferred-needed capabilities and exits non-zero when any over-declaration is found, so it can gate least-privilege in CI. Deliberately no information-flow / data-visibility typing (a large type-system addition; deferred). Seedocs/design-capability-inference.md.
[1.8.0] — 2026-07-15
Eighth feature minor on the 1.x LTS line. Third sprint of the agentlanguages.dev competitive-borrow program (Theme C-lite): the LLM effect now propagates up the call graph and is recorded in evidence.
Added
- LLM effect propagates up the call graph; model-invoking steps recorded in evidence. Theme C-lite — borrowed from Vera (LLM inference as a tracked typed effect). New
Module::transitively_invokes(func_idx, capability)computes whether a function reaches a capability through its call graph (its own declared capabilities, or any function itCalls /SpawnActors, transitively — cycle-safe, order-independent). When a workflow runs, each source-kind step is analysed for transitivellm.callreachability and the sorted list of model-invoking step ids is captured into the evidence bundle asmodel_invoking_steps.json— checksummed and covered bybundle_hash, soevidence verifyfails on tamper. An auditor can now see which steps touched a model, even when the call is indirect through a helper. Deliberately no conformal-prediction / uncertainty quantification (research-grade; deferred). Seedocs/design-llm-typed-effect.md.
[1.7.0] — 2026-07-15
Seventh feature minor on the 1.x LTS line. Second sprint of the agentlanguages.dev competitive-borrow program (Theme A-lite): runtime-checked contracts with concrete, replayable counterexamples — no SMT.
Added
- Runtime-checked
requirespreconditions with counterexample evidence. Sprint 2 / Theme A-lite of the agentlanguages.dev competitive-borrow program — borrowed from Vera/Aver (design-by-contract) and Vow (counterexample = concrete replayable input). A function’srequires <expr>clauses are now compiled to runtime guards checked at entry against the arguments; a violation traps with a newVmError::ContractViolation { message, counterexample }wherecounterexampleis the offending argument list (positional, rendered) — the exact input an auditor needs to reproduce the breach. The failure surfaces with a stable, distincterror_kindcontract_violation(retry: no — a violation is a deterministic function of the inputs), and because failed-step errors are recorded in the hash-chained audit log, the counterexample lands in tamper-evident evidence. Reuses the previously-dormantOp::Assertopcode (no bytecode bump); functions without contracts emit no guard. Deliberately NO SMT/Z3 — this stays in Boruna’s concrete-trace + replay philosophy, consistent with the 1.5.0 “Decided” ruling against symbolic model checking.ensurespostconditions (needing aresultbinding at each return) are a documented follow-up. Seedocs/design-contracts-runtime.md.
[1.6.0] — 2026-07-15
Sixth feature minor on the 1.x LTS line. First sprint of the agentlanguages.dev
competitive-borrow program (Theme B): machine-read intent declarations captured
into tamper-evident evidence bundles.
Added
intent "..."declarations captured into evidence bundles. Borrowed from Pact (intent-in-signature) and Intent/Prove (machine-read purpose), per the agentlanguages.dev competitive research (claudedocs/research_agentlanguages_competitive_2026-07-15.md, Theme B). A function may declare a single machine-read purpose after its signature:fn transfer(x: Int) -> Int !{db.write} intent "Move funds between accounts" { ... }. The clause is optional, order-independent withrequires/ensures, and a secondintenton one function is a parse error. Intent threads through lexer → AST (FnDef.intent) → codegen → bytecode (Function.intent, additive#[serde(default)]— pre-Sprint-1 modules load withNone) and is surfaced inboruna ast --json. When a workflow runs, each source-kind step’sintentis captured into the evidence bundle asintents.json(step_id → declared purpose), covered by the bundle’s checksums andbundle_hash— soevidence verifyfails if a captured intent is tampered, and an auditor sees what each step was authorized to do next to what it did. Determinism (§15): intent is replay-verified evidence, already transitively inworkflow_hash. Seedocs/design-intent-evidence.md.
[1.5.0] — 2026-05-20
Fifth feature minor on the 1.x LTS line. Quint-inspired tooling additions:
literate workflow specs, ITF trace export, an interactive REPL,
random property-based workflow simulation with witnesses, and the
__builtin_debug print-and-passthrough helpers. The first bytecode
minor bump (1.0 → 1.1) under the additive-opcode contract.
Added
- Literate workflow specs (
boruna literate extract) — borrowed from Quint’s Literate Specifications. A markdown file with<lang> <filename> +=code fences (where<lang>∈ax|boruna|quint) is the single source of truth for both the audit narrative AND the executable Boruna source.boruna literate extract <file.md> --out-dir <dir>walks the document, validates each fence, and emits per-file outputs that compile and run via the normalboruna run/workflow runpaths. Idempotent: re-running produces byte-identical output. Path traversal and absolute paths are rejected at parse time with stableerror_kindstrings (literate.invalid_fence,literate.path_traversal,literate.absolute_path,literate.invalid_out_dir,literate.io). New moduletooling/src/literate/, reusable from any caller includingboruna-mcp. Example fixture atexamples/literate/hello_literate.md. Seedocs/design-literate-workflows.mdanddocs/architecture-literate-workflows.md. - ITF (Informal Trace Format) export from evidence bundles — borrowed from Quint / Apalache / the ITF Trace Viewer.
boruna evidence inspect <bundle> --itfemits the bundle’s audit log as an ITF v0.15 document on stdout, with one ITF state per audit-log entry and event variant names preserved as#meta.action.--itfis mutually exclusive with--json. Boruna’s internal evidence-bundle format is unchanged — ITF is purely an export. Vendored producer constantITF_FORMAT_VERSION = "0.15". New moduletooling/src/trace/{itf,audit_to_itf}.rs. Spec source: https://apalache-mc.org/docs/adr/015adr-trace.html. Seedocs/design-itf-traces.mdanddocs/architecture-itf-traces.md. __builtin_debug(v)/__builtin_debug_msg(msg, v)— print-and-passthrough debug helpers (bytecode 1.1). Borrowed from Quint’sq::debug. The single-arg form printsValue::Displayform to stderr and returns the value unchanged; the two-arg form prints<msg> <value>\n. Operational-only — no capability gate, no audit-log event, no replay impact. Implemented as two new opcodesOp::Debug(0xA7) andOp::DebugMsg(0xA8);BYTECODE_VERSIONbumped to"1.1". A 1.0 reader presented with either opcode MUST reject with an unknown-opcode error per §1.2(6) ofdocs/spec/bytecode-1.0.md. Seedocs/architecture-q-debug.md.boruna repl [file.ax]— interactive REPL for.axmodules. Borrowed from Quint’squint repl. Loads an optional initial.axfile, evaluates expressions, supports meta-commands:load,:reload,:reset,:type,:env,:help,:quit. Per-input compile + fresh VM (avoids module-frozen-by-construction VM invariants); the synthetic wrapper declaresIntreturn type because the typechecker is currently permissive about return-type unification, and:typereports the post-hocValue::type_name(). Defaults to--policy deny-allbecause REPL inputs are non-deterministic. No line-editing dep — usesstd::io::BufRead, sufficient for piped agent-driven use. Bytecode 1.1’s__builtin_debugworks in the REPL. Seedocs/architecture-boruna-repl.md.boruna simulate <dir> [--invariant <expr>] [--witnesses name=expr,...]— random property-based workflow simulation. Borrowed from Quint’squint run. Runs the workflow--max-samplestimes (1..=100_000, default 1000) and reports invariant violations + per-witness trace frequencies. The invariant / witness DSL acceptsstatus == "...",total_duration_ms < N,step.<id>.status == "...",step.<id>.duration_ms < N, combined via&&/||with parentheses. Perproject-conventions-2026-04§15 the simulator’s per-traceWorkflowRunResultis operational-only and never feeds production replay verification. Sequential v1; input fuzzing and parallel execution are documented follow-ups. Stableerror_kindstrings:simulate.invalid_samples,simulate.invalid_workflow,simulate.invariant_parse,simulate.witness_parse. New moduleorchestrator/src/simulate/{mod,invariant,witness}.rs. Seedocs/architecture-boruna-simulate.mdanddocs/architecture-boruna-witnesses.md.
Decided
docs/spec/bytecode-1.0.mdv1.1 minor bump — additive opcodes per §1.2(6) of the spec. Updates the version-identifier prose, adds §4.5 “1.1 additions” with the new opcode table entries (Debug 0xA7,DebugMsg 0xA8), and appends a §12 changelog entry. Backwards compatibility within 1.x is preserved: a 1.1 module CAN be rejected by a 1.0 reader (unknown-opcode error), but every 1.0 module continues to load on a 1.1 reader.- Apalache-style bounded symbolic model checking is NOT recommended for Boruna. A pull-in of Apalache + Z3 + a Boruna IR → SMT translator would be a multi-engineer-year integration aimed at an audience (consensus-protocol provers) that does not appear in Boruna’s compliance-runtime positioning. Boruna’s concrete-trace + replay + evidence-bundle model is a different design philosophy and stays as-is. See
claudedocs/research_quint_borrowable_ideas_2026-05-20.md.
[1.4.0] — 2026-05-17
Fourth feature minor on the 1.x LTS line. Agent-native CLI inspection surfaces,
the boruna-lsp language server, and compliance example workflows.
Added
- Agent-native CLI surfaces — five read-only,
--json-capable commands so AI agents can inspect Boruna projects without reading source. Motivated by a competitive review ofvercel-labs/zero.boruna lang codes [--json]— emit the registry of stable diagnostic codes (E001–E009) with name, summary, and category. Backed bytooling/src/diagnostics/registry.rs; a drift test keeps the registry 1:1 with theE0NNconstants the compiler emits.boruna doctor [--json]— environment and toolchain health: binary version, compiled features, Rust toolchain, data-directory writability, and project-layout detection. Exits 1 if any check fails.boruna workflow graph <dir> [--json]— emit DAG facts for a workflow: nodes (kind, capabilities, dependencies), edges, topological order, roots, and leaves. Exits 1 on a non-DAG.boruna size <file.ax> [--json]— bytecode artifact cost: per-function opcode counts, module-wide totals, and serialized.axbcbyte size.boruna skills list/boruna skills get <name> [--json]— embedded, agent-curated documentation (ax-language,cli,workflows,diagnostics) compiled into the binary, usable with no repository checkout.
docs/reference/diagnostic-codes.md— human reference for the diagnostic-code registry.boruna-lsplanguage server — new cratecrates/boruna-lsp. A Language Server Protocol implementation for.axfiles providing live diagnostics, completion, and formatting in any LSP-capable editor (VS Code, Neovim, …). Seedocs/guides/lsp.md.- Compliance example workflows — three regulated-use-case workflows under
examples/compliance/:soc2_audit_workflow(SOC 2 audit trail),hipaa_data_pipeline(PHI redaction + audit log),financial_review_pipeline(dual-control SOX approval gates). Seedocs/reference/compliance/README.md.
[1.3.0] — 2026-04-30
Stable
std-llmis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-llm.md; bumps require a 1.x deprecation notice per LTS contract.std-jsonis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-json.md; bumps require a 1.x deprecation notice per LTS contract.
Added
- 27 new language built-in functions — comprehensive string, list, and map operations now available in
.axprograms without importing any library:- String:
__builtin_int_to_string,__builtin_float_to_string,__builtin_string_len,__builtin_string_chars,__builtin_string_contains,__builtin_string_starts_with,__builtin_string_ends_with,__builtin_string_to_upper,__builtin_string_to_lower,__builtin_string_trim,__builtin_string_join,__builtin_string_split,__builtin_string_replace,__builtin_string_slice,__builtin_int_parse,__builtin_float_parse,__builtin_bool_to_string - List:
__builtin_list_len,__builtin_list_is_empty,__builtin_list_head,__builtin_list_tail,__builtin_list_append,__builtin_list_concat,__builtin_list_reverse - Map:
__builtin_map_get,__builtin_map_set,__builtin_map_remove,__builtin_map_contains_key,__builtin_map_keys,__builtin_map_values,__builtin_map_len
- String:
- Import resolution —
import "std-name"statements in.axsource now resolve at compile time via a source-level preprocessor that inlines the named library fromlibs/<name>/src/core.ax. No compiler pipeline change required. boruna evidence inspectshows step outputs — for plaintext bundles,evidence inspect <bundle>now readsoutputs/<step_id>/result.jsonand renders a truncated preview (500 chars) per step in text mode;--jsonmode includes a"step_outputs"key with full parsed content. Encrypted bundles without--decryptprint a hint to stderr.std-jsonenhancements —json_array(items: List<String>) -> Stringserializes a list to a JSON array string;int_to_stringnow calls__builtin_int_to_string(was returning empty string);json_escapenow performs proper character-by-character escaping using__builtin_string_chars.std-validationenhancements —string_lengthnow calls__builtin_string_len(was hardcoded 0); addedvalidate_contains,validate_starts_with,validate_ends_with.
1.2.0 — 2026-04-29
Stable
std-uiis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-ui.md; bumps require a 1.x deprecation notice per LTS contract.std-validationis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-validation.md; bumps require a 1.x deprecation notice per LTS contract.std-formsis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-forms.md; bumps require a 1.x deprecation notice per LTS contract.std-authzis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-authz.md; bumps require a 1.x deprecation notice per LTS contract.std-httpis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-http.md; bumps require a 1.x deprecation notice per LTS contract.std-dbis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-db.md; bumps require a 1.x deprecation notice per LTS contract.std-syncis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-sync.md; bumps require a 1.x deprecation notice per LTS contract.std-routingis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-routing.md; bumps require a 1.x deprecation notice per LTS contract.std-storageis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-storage.md; bumps require a 1.x deprecation notice per LTS contract.std-notificationsis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-notifications.md; bumps require a 1.x deprecation notice per LTS contract.std-testingis now 1.0-stable. Public surface frozen perdocs/reference/stdlib/std-testing.md; bumps require a 1.x deprecation notice per LTS contract.
Added
- Compliance templates — three pre-built workflow patterns:
soc2_audit_workflow(SOC 2 audit trail),hipaa_data_pipeline(PHI redaction + audit log),financial_review_pipeline(dual-control SOX approval gates) - Four new example workflows demonstrating stdlib package usage (
form_submission_pipeline,data_ingestion_pipeline,api_routing_workflow); closes graduation criterion 1 for all 11std-*packages docs/reference/stdlib/std-llm.mdanddocs/reference/stdlib/std-json.md— reference docs closing criterion 4 forstd-llmandstd-jsonexamples/workflows/llm_content_generator/andexamples/workflows/json_data_transformer/— example workflows closing criterion 1 forstd-llmandstd-jsonboruna evidence diff— compare two evidence bundles side-by-side. Reports differences in step outputs, audit event counts, workflow metadata, and verification status.--jsonflag for machine-readable output.boruna workflow eval— run the same workflow against two LLM provider configs and compare evidence bundles; reports per-step output agreement and timing
Changed
- Improved error messages:
boruna lang checknow suggests the nearest variable name for E003 errors and the nearest function name for E004 errors using edit-distance-1 matching; type-conversion hints for common E009 mismatches (Int↔String, Bool↔Int); E001 lexer errors now include a source pointer line; E002 parse errors append a common-cause hint; E007 capability violation message now names the offending capability and action. - Better
lang repair: repair now handles E003 near-miss rename patches via the tooling suggestion pipeline; newRepairStrategy::Conservativeapplies onlyHigh-confidence patches, skippingMedium/Low(safe for CI auto-repair); bottom-up patch ordering was already in place and verified correct.
1.1.0 — 2026-04-29
Added
-
Capability call markers in MCP progress notifications (post1-T-2.2).
boruna_runstreaming progress events now carry amessagefield when a capability call fires during an execution slice:"cap: llm.call"for a single call or"caps: llm.call, net.fetch"for multiple. Slices with no capability calls continue to omitmessage(no noise for pure compute). MCP clients that display live execution status can now surface"calling llm.call…"feedback without polling. Backward-compatible: existing clients that ignoremessagesee no change. -
Web evidence bundle inspector (post1-T-4.4). New
boruna evidence serve <bundle-dir> [--port <port>]subcommand (requiresservefeature) starts a local axum HTTP server on port 4444 and opens the browser automatically. Pages:/bundle(overview + verification status + file checksums),/audit(hash-chained event timeline),/outputs(per-step result JSON accordion),/api/bundle(raw JSON dump). Bundle data is loaded once at startup; verification runs via the existingverify_bundle()path and surfaces PASS/FAIL inline. Works offline — no external CDN dependencies. -
BYOH reference handler library (post1-T-1.2). Four new reference
CapabilityHandlerimplementations underexamples/llm_handlers/joining the existing OpenAI example: Anthropic (Messages API), Ollama (local-LLM, deterministic-with-seed), vLLM-and-OpenAI-compatible (one handler covers vLLM/OpenRouter/ Together/Groq/LiteLLM), and AWS Bedrock (skeleton using the AWS SDK because hand-rolling SigV4 adds nothing illustrative). Each is a self-contained ~80–120-LOC copy-and-tweak template with a README documenting auth, response shape, determinism options, and what the reference deliberately omits (multi-provider routing, streaming, cost accounting, etc.). New umbrellaexamples/llm_handlers/README.mdindexes the library and cross-references the built-inLlmRouterHandler(sprint 0.4-S13). Newproviders.toml.exampledocuments a config-schema convention integrators can adopt for declarative router setup (Boruna does not parse this file); newrouter_setup.rsshows a reference parser that turns the toml into anLlmRouterHandler.This expansion is faithful to the BYOH design contract shipped in 0.3-S8: Boruna does not ship default handlers in core. Each reference is integrator-copyable code, not a Cargo dep. Auditing the original T-1.2 plan (“ship a
boruna-effect-providersadapter crate”) against the shipped BYOH guide flagged the premise conflict before any code was written; the reframed scope delivers the spirit of “more provider on-ramps” without violating the contract.
Changed
BundleStoragetrait promoted to public 1.x API surface.BundleStorage,StorageRef,StorageError,LocalFs, and thefrom_uridispatcher inboruna_orchestrator::audit::storageshipped behind#[doc(hidden)]while the shape was still being validated against remote impls. With T-3.1 (S3), T-3.2 (GCS), and T-3.3 (Azure Blob) all landed and exercising the trait identically, the shape is stable and the hidden attribute is removed. The per-adapter modules (storage_s3/storage_gcs/storage_azure) ship without#[doc(hidden)]from the start, so this change is purely a rustdoc visibility tweak — no API breakage.StorageErroris now#[non_exhaustive]so future variants are additive. Backendkindstrings (s3.transient,azure.permanent, etc.) are also additive — integrators switching onkindshould treat unknown values astransient(retryable). New top-level concept page atdocs/concepts/bundle-storage.mdcovers the shared contract; the per-provider operator guides remain indocs/guides/bundle-storage-{s3,gcs,azure}.md.
Decided
- Stdlib graduation tracker (post1-T-3.4). Assessed all 11
std-*packages against the 4-criterion graduation checklist. Zero packages graduate to 1.0 this cycle. Two criteria fail uniformly: none of the packages is referenced from anyexamples/workflows/*, and none has adocs/reference/stdlib/<name>.mdreference page. Per-package decisions and per-criterion notes are recorded indocs/stdlib-graduation-tracker.md. Closing the gates is filed as Wave-3 follow-up work.
Added
- Azure Blob Storage adapter for
BundleStorage(post1-T-3.3, Wave 3). The--bundle-storage azblob://account/container[/prefix]URI now constructs an Azure Blob Storage adapter when the binary is built with theazurefeature (cargo build --features boruna-cli/azure). Same shape as the T-3.1 / T-3.2 adapters, also backed byobject_store(with theazurefeature toggled). URI shape encodes both the storage account and the blob container so an operator can grep their config and see exactly which account a bundle landed in. Auth via standardAZURE_STORAGE_*env vars (account key, SAS, or service-principal OAuth);AzureBlobBucketBuilder::with_use_emulator(true)switches the SDK into Azurite-emulator mode for local testing. Off by default. When theazurefeature is OFF,azblob://URIs reject at parse time with the actionable-message pattern S3 and GCS use. Backend errors surface with stableerror_kindstrings (azure.transient,azure.permanent,azure.runtime,azure.unexpected_key). 18 unit tests cover URI parsing, object-path concatenation, ref-to-run-id extraction, and error classification. An Azurite-backed integration test is deferred — Azurite requires SharedKey-signed container creation and object_store doesn’t expose acreate_containerprimitive; pulling in the fullazure-storagecrate or implementing SharedKey signing for one test wasn’t a proportionate cost. Seedocs/guides/bundle-storage-azure.md. All three remote schemes (S3, GCS, Azure) now ship — theBundleStoragetrait can graduate from#[doc(hidden)]topubin a follow-up. - GCS adapter for
BundleStorage(post1-T-3.2, Wave 3). The--bundle-storage gs://bucket[/prefix]URI now constructs a Google Cloud Storage adapter when the binary is built with thegcsfeature (cargo build --features boruna-cli/gcs). Same shape as the T-3.1 S3 adapter, also backed byobject_store(with thegcpfeature toggled). Auth via standardGOOGLE_SERVICE_ACCOUNT/GOOGLE_APPLICATION_CREDENTIALSenv vars;GcsBucketBuilder::with_endpointlets integration tests point at fake-gcs-server. Off by default. When thegcsfeature is OFF,gs://URIs reject at parse time with the same actionable-message pattern S3 uses. Backend errors surface with stableerror_kindstrings (gcs.transient,gcs.permanent,gcs.runtime,gcs.unexpected_key). Integration tests behind thegcs-itfeature spin upfsouza/fake-gcs-servervia a custom testcontainers Image (testcontainers-modules has no GCS module) and self-skip when Docker is unreachable. Seedocs/guides/bundle-storage-gcs.md. - S3 adapter for
BundleStorage(post1-T-3.1, Wave 3). The--bundle-storage s3://bucket[/prefix]URI now constructs a real remote-storage adapter when the binary is built with thes3feature (cargo build --features boruna-cli/s3). Backed by the Apache Arrowobject_storecrate’sawsfeature — works against AWS S3, MinIO, Cloudflare R2, Backblaze B2, and LocalStack via the standardAWS_*environment variables (includingAWS_ENDPOINT_URLfor non-AWS endpoints). The adapter bridges the syncBundleStoragetrait against the async SDK with a per-instance current-thread tokio runtime; bundle reads materialize into a local cache directory rooted atBORUNA_BUNDLE_CACHE(defaults to<temp>/boruna-bundle-cache). When thes3feature is OFF,s3://URIs reject at parse time with an actionable message that points operators at the feature flag — never silently ignored.gs://(T-3.2) andazblob://(T-3.3) remain reserved for upcoming adapters. Backend errors surface with stableerror_kindstrings (s3.transient,s3.permanent,s3.runtime,s3.unexpected_key). MinIO-backed integration tests live behind thes3-itfeature and self-skip when Docker is unreachable. Seedocs/guides/bundle-storage-s3.md. boruna evidence rotate-kek(post1-T-2.4) re-wraps the DEK of one or more encrypted evidence bundles under a new KEK. Operations are manifest-only — per-file ciphertext stays valid because the DEK itself is unchanged. Supports single-bundle and batch (directory) modes; batch mode runs in parallel via rayon, bounded by--parallelism N(defaultmin(8, num_cpus)).--dry-runvalidates without writing.--kek-id-from <id>defends against accidental double-rotation in mixed-state batches. NewEnvelope::rewrapAPI on the encryption module exposes the same primitive to library consumers. Seedocs/guides/kek-rotation.md.- Pluggable evidence-bundle storage trait
BundleStorageand aLocalFsadapter (post1-T-2.3).boruna workflow run --recordnow accepts--bundle-storage <uri>(orBORUNA_BUNDLE_STORAGEenv var); when set, the finalized bundle is copied to the configured backend after the local write succeeds. Storage failure is logged but never fails the workflow — the local bundle remains the authoritative record. Only thelocal:<root>scheme ships in this release;s3://,gs://,azblob://are reserved for Wave 3 adapters and reject at parse time. The trait is#[doc(hidden)]until at least one remote adapter ships. boruna_runMCP progress notifications are now part of the 1.x LTS-stable surface (post1-T-1.1). When a client supplies the standard MCPprogressTokenin the request’s_metafield, the server drives the VM in ~100k-opcode slices and emitsnotifications/progressevents between slices with the cumulative step count. The underlying mechanism shipped in sprint 0.4-S6; this entry formalizes the wire contract and adds reference docs indocs/reference/mcp-server.md§ “Progress notifications”.- Worker capability advertisements now carry an optional version
(post1-T-1.3).
RegisterRequest.advertised_capabilitiesaccepts either a bare string (legacy worker, normalized by the coord to the coord’s currentCapability::version()) or an explicit{name, version}object. Steps requiring a capability whose version no registered worker advertises now surfacecoord.capability_version_mismatch(HTTP 409) on the claim response, rather than silently long-polling. The W3-A silent-skip path still applies when a worker is missing the capability NAME entirely. Documented indocs/reference/error-kinds.mdand noted indocs/spec/workflow-dag-1.0.md.
Changed
- Parser and typeck error messages now include
did you mean: 'kw'?suggestions for typos within Levenshtein distance 1 of a known keyword (parser) or in-scope identifier (typeck). Suggestions are appended only when a single unique candidate exists, so noisy ambiguous suggestions are intentionally suppressed. The single-line prefix (undefined variable: foo) remains unchanged so existing diagnostic-collector parsers continue to classify errors correctly.
Added
docs/post-1.0/README.mddescribing how post-1.0 work is tracked on GitHub: branch naming, labels, and the project-board scheme.docs/branch-policy.mddocumenting themaster(1.x LTS) vs.0.7.x(speculative) branch topology and cross-merge rules.- GitHub labels
wave-1…wave-4,branch-master,branch-0.7.x,post1-executionfor filtering post-1.0 PRs and issues. Bench compareCI job and.github/scripts/bench_compare.py— PR-time perf-regression detection that runs the criterion harness on PR base and head, posts a sticky comment with per-benchmark deltas, and fails on ≥10% mean regression. Non-blocking by default (not in the required-status-checks set). SeeCONTRIBUTING.md§ “Reading the bench-compare PR comment”.Smoke test (musl)CI workflow and.github/scripts/smoke_musl.sh— automated container-based smoke tests for thex86_64-unknown-linux-muslandaarch64-unknown-linux-muslrelease artifacts. Runs on everyv*-rc*tag (or viaworkflow_dispatchfor an existing tag), verifies SHA-256, launches the binary underalpine:3.19, runs thellm_code_reviewexample end-to-end, verifies the evidence bundle, and opens a PR withdocs/release-smoke-tests/<tag>-musl-<arch>.mdreports. The aarch64 leg runs under qemu-user-static and is explicitly NOT a real-hardware smoke; that remains operator-side.boruna run --watch— re-execute a.axfile on every change. Debounces filesystem events to 200ms, prints a── reloading <path> at HH:MM:SS ──separator before each rerun, and tolerates per-run errors so the watcher keeps running across fix-and-save cycles. Seedocs/reference/cli.md§ Watch mode.
1.0.0 - 2026-04-28
First stable release. The 1.x LTS contract takes effect from
this tag forward — every 1.0 .ax program, workflow.json,
evidence bundle, MCP integration, and CLI invocation is committed
to keep working on every 1.y release per docs/lts.md
§B.
Same surface as 1.0.0-rc3. No code changes between rc3 and
this GA cut; this tag exists to crystallize the 1.0 LTS commitment
and ship final-named binaries.
The four formal versioned specifications frozen at 1.0:
docs/spec/ax-language-1.0.md—LANGUAGE_VERSION = "1.0"docs/spec/bytecode-1.0.md—BYTECODE_VERSION = "1.0"docs/spec/workflow-dag-1.0.md—WORKFLOW_DAG_SCHEMA_VERSION = 1docs/spec/evidence-bundle-1.0.md—BUNDLE_FORMAT_VERSION = "1.0"
For the full feature scope shipped between v0.5.0 and v1.0.0,
see the [1.0.0-rc1], [1.0.0-rc2], and [1.0.0-rc3] sections
below.
Decided
- 1.x LTS contract is now in force.
docs/lts.md§B surfaces are stable through 2027-11 (active) / 2028-05 (security). Surfaces classified Experimental indocs/stability.mdremain Experimental within 1.x; pin to a specific Boruna release tag if your integration depends on those.
[1.0.0-rc3] - 2026-04-28
Theme: final GA-readiness polish. rc2 shipped W6 (mTLS + bundle encryption) and W7 (security-review closures). rc3 folds the W8-W11 GA-polish work into a tagged candidate so operators have a single artifact representing the actual GA candidate to soak. Highlights:
- 4th formal versioned specification (bytecode 1.0)
publishes alongside the existing three (.ax language,
workflow DAG, evidence bundle), all locked behind reader
constants per
docs/lts.md§B. - Algorithm-gate enforcement in evidence bundle decryption
(W7 NEW-1):
Envelope::unwrapnow rejects bundles declaring algorithm ≠ aes-256-gcm withevidence.unsupported_algorithm, before any KEK-related work — matches the spec’s reader contract. - CI hardening: bench harness compiles on every PR (W8); examples run end-to-end + verify on every PR (W9-D); parallel-test flakes fixed (W10).
- Operator-facing GA-cut tooling:
scripts/pre-release-check.shis the single command that confirms GA-readiness before tagging (W11-A). - CHANGELOG-driven release notes (W9-B): the GitHub Release page body is now the CHANGELOG section for the tag, not auto-generated commit noise. First release using this flow is rc3 itself.
After rc3 soak, the v1.0.0 GA tag is a 5-min coding step:
bash scripts/pre-release-check.sh 1.0.0 → bump to 1.0.0
→ tag → push.
Added
- Versioned bytecode 1.0 specification at
docs/spec/bytecode-1.0.md(sprintW9-A).bytecode_version: "1.0"exposed viaboruna_bytecode::BYTECODE_VERSION. Locks the on-disk module format, opcode table, value model, capability table, and determinism contract for the 1.x line. Forward-compat: 1.x VMs accept any 1.y bytecode module. - CHANGELOG-driven GitHub Release notes (sprint
W9-B). The release pipeline now extracts the CHANGELOG section for the current tag and uses it as the GitHub Release body instead of auto-generating from commits. Operators MUST updateCHANGELOG.mdbefore tagging — empty section fails the release loudly. Improves release-page readability for integrators. - End-to-end smoke gate for example workflows in CI
(sprint
W9-D). Each example workflow underexamples/workflows/now runs to completion with--policy allow-all --recordand the produced bundle isevidence verify-ed on every push/PR. Catches integration regressions where DAG validation passes but execution fails. cargo bench --no-rungate in CI (sprintW8). The criterion bench harness now compiles on every push/PR so refactors that break bench compilation surface at PR time instead of at the next operator-run baseline.- Pre-release validation script at
scripts/pre-release-check.sh(sprintW11-A). Read-only script the operator runs before tagging that confirms repo state, version alignment, CHANGELOG coverage, all spec constants, every CI gate, and the examples smoke flow. evidence.unsupported_algorithmtyped error (sprintW7NEW-1).Envelope::unwrapnow rejects bundles with an algorithm field other thanaes-256-gcmBEFORE any KEK work — matches theevidence-bundle-1.0.mdreader contract. Closes the spec/code gap flagged by the W7 follow-up security review.- Smoke-test report for v1.0.0-rc2 macOS arm64 artifact at
docs/release-smoke-tests/v1.0.0-rc2.md(sprintW9-C). End-to-end verification of the published GitHub Releases binary; pre-GA sign-off for the macOS arm64 target. Linux musl targets remain operator smoke tests on real hardware. - 9 missing MCP-layer
error_kindstrings added todocs/reference/error-kinds.md(sprintW7NEW-2): closes the taxonomy completeness gap flagged by the W7 follow-up security review. The doc now enumerates 36+ stableerror_kindstrings acrosscoord.*,evidence.*,workflow.*,policy.*, and MCP-layer namespaces.
Changed
scripts/ci.shrefreshed (sprintW11-A) to match the current.github/workflows/ci.yml: clippy--all-targets(W1-A), serve-feature clippy run, bench compile gate (W8).docs/INTEGRATION_GUIDE.mdv0.1.0 references replaced with v1.0-GA-aware framing (sprintW10H-1). The body of the guide remains structurally accurate for v1.0; only the trailing “What Boruna Does Not Do” section was patched.docs/FRAMEWORK_API.mdversion label dropped (sprintW10H-2). The framework crate is in the Experimental stability tier perdocs/stability.md; the doc now cross- links to that tier definition + the LTS contract instead of carrying a misleading(v0.1.0)heading next to the workspace’s 1.0.0-rc tag.
Decided
- Cut a third release candidate (
v1.0.0-rc3) instead of GA directly (sprintW11). rc2 was published before W7-W11 work landed; cutting GA on current master would skip the soak window entirely and lock the LTS contract on unverified-in-field surfaces (notably the W7 NEW-1 algorithm gate change inEnvelope::unwrap). rc3 represents the actual GA candidate; soak runs against rc3, then GA cut.
[1.0.0-rc2] - 2026-04-28
Theme: GA polish. rc1 shipped the post-v0.5 sprint cycle
(W1-W6: versioned spec freezes, coord HA, mTLS, capability
tagging, blob GC, scaffold, perf baselines, LTS commitment,
migration tooling, bundle encryption). rc2 closes the security
review remainder before the v1.0 GA tag: explicit GA decision on
TLS 1.2 (kept, with rationale); spec amendment documenting the
optional encryption block in evidence bundles; canonical
error_kind taxonomy reference; mTLS guide updated with
revocation + non-ASCII CN limitations; evidence inspect
plaintext-leak gate test; algorithm-gate enforcement in
Envelope::unwrap matching the spec’s reader contract;
stdlib version policy clarified.
Added
- Canonical
error_kindtaxonomy reference atdocs/reference/error-kinds.md(sprintW7). All stableerror_kindstrings enumerated with HTTP status, sprint origin, and caller-facing meaning per LTS §B.6. Cross-linked from the policy-schema reference and the evidence bundle spec; integrators may switch on these strings. - Evidence bundle encryption block documented in spec (sprint
W7, findingM-1).docs/spec/evidence-bundle-1.0.mdnow formally describes the optionalencryptionfield added inW6-B: field shape, AES-256-GCM algorithm pin, per-file nonce derivation, replay-verified vs. operational classification, and the reader contract (1.x readers WITH KEK, 1.0 readers without). Additive toformat_version: 1.0; no version bump. - mTLS limitations documented (sprint
W7, findingsM-4/M-5).docs/guides/coord-mtls.mdgains a “Limitations” section calling out the absence of CRL/OCSP revocation and recommending short-lived (≤24h) certs as the v1 mitigation, plus a “CN comparison semantics” subsection documenting the ASCII-onlyeq_ignore_ascii_casefold (non-ASCII CNs are case-sensitive; no Unicode normalization). - Mutual TLS auth + per-worker client certificates (sprint
W6-A). Operators can now require X.509 client certs on the coord HTTP surface via--tls-cert,--tls-key, and--tls-client-ca. Workers present client certs via--tls-cert/--tls-key/--tls-server-ca. The cert subject CN drives worker identity; mismatch with a bodyworker_idreturnscoord.identity_mismatch. mTLS is additive: shared-secret bearer auth (sprint 0.5-S3) continues to work unchanged. Operator guide:docs/guides/coord-mtls.md. New error_kind:coord.identity_mismatch. - Evidence bundle encryption (sprint
W6-B). Operators can now opt into AES-256-GCM envelope encryption for evidence bundles viaboruna workflow run --record --encrypt-bundlewith the KEK supplied via--bundle-encryption-key <hex>orBORUNA_BUNDLE_KEKenv. Per-bundle data keys (DEK) are wrapped with the KEK;bundle.jsoncarries the wrapped DEK and algorithm metadata.verify_bundleauto-detects encryption and decrypts before integrity check. Backwards- compat: unencrypted bundles continue to work. KEK lifecycle is the operator’s responsibility — Boruna does not manage keys. New error_kinds:evidence.encryption_key_required,evidence.encryption_key_mismatch,evidence.cipher_tag_invalid. Threat model:docs/design-bundle-encryption.md.
Decided
- TLS 1.2 remains enabled in W6-A mTLS (sprint
W7). Decision: the default rustls 0.23 +aws_lc_rsconfiguration restricts TLS 1.2 to AEAD-only ciphers (no CBC, no RC4, no export-grade), which is considered safe for 1.0 GA. Operators wanting TLS-1.3-only can build with a forkedrustlsfeature set; the default ships TLS 1.2 for compatibility with older HTTP load balancers and worker hosts. Rationale: the cryptographic surface (AEAD ciphers, ECDHE key exchange) is the same as TLS 1.3 for the practical attack model; forcing TLS-1.3-only would block deployment on systems with older client/proxy stacks. Re-evaluate at 2.0 if TLS 1.3 adoption is universal.
[1.0.0-rc1] - 2026-04-28
Theme: 1.0 release candidate. This is the first 1.0
release candidate. Surfaces listed in
docs/lts.md section B are now LTS-protected
under the long-term-support contract that takes effect at 1.0
GA. Three formal versioned specifications are published and
frozen at 1.0: the .ax language, the workflow DAG schema,
and the evidence bundle format. The distributed-execution
stack from v0.5.0 ships HA-ready (multi-coord active-active
behind a load balancer or via worker URL failover). Workers
can advertise capability subsets so heterogeneous fleets are
supported. Operators get the boruna new interactive
scaffold, boruna migrate for upgrading legacy artifacts,
and boruna evidence gc-blobs for blob storage cleanup.
Performance baselines are published with 1.x budget
commitments.
Added
-
boruna migratesubcommand (beta) (sprintW5-C). Migrators for evidence bundles (synthesize missingbundle.jsonfor legacy v0.5.0-and-earlier bundles) and workflow.json (addschema_version: 1when missing).--dry-runpreviews;--in-placemodifies the input -
boruna migratesubcommand (beta) (sprintW5-C). Migrators for evidence bundles (synthesize missingbundle.jsonfor legacy v0.5.0-and-earlier bundles) and workflow.json (addschema_version: 1when missing).--dry-runpreviews;--in-placemodifies the input directly; default writes a.migratedsibling. Beta status: the migrator coverage will expand in 1.x as breaking changes accumulate. Operator guide:docs/guides/migration.md. -
Performance benchmarks baseline (sprint
W5-A). Newbenches/workspace member withcriterion-based benchmarks for compile time, VM throughput, and evidence bundle write/verify. Documented baseline + 1.x performance budget commitments atdocs/PERFORMANCE.md. Benches are not gated in CI; run locally viacargo bench -p boruna-benches. -
Long-term-support contract for 1.x (sprint
W5-B). Newdocs/lts.mddocuments the support windows (1.x active for 18 months from 1.0 GA, security-supported for 24 months; 0.x EOL on 1.0 GA), the LTS-protected surface (.axlanguage_version: "1.x", workflow DAG schema, evidence bundle format, MCPprotocol_version: 1responses, CLI commands and flags,error_kindstrings, HTTP API wire format), the deprecation policy (announce in 1.y → runtime warning → 6-month notice → migration tooling) for breaking changes in 2.x, the security-fix backport policy (CVSS v4, CRITICAL/HIGH within 7 days), and the 12-month end-of-life procedure.docs/stability.mdcross-links to the LTS contract and clarifies which tiers are LTS-protected. The README gains an LTS line near the badges.SECURITY.mdgains a backport-policy section. Doc-only sprint, no code changes. -
Worker capability tagging (sprint
W3-A). Workers may advertise a SUBSET of the coord’s capability set via--advertise-caps net.fetch,db.query; coord routes only steps whose policy-required capabilities are a subset of the worker’s advertised set. Backwards-compatible: workers omitting the flag behave as before (full fleet). Newerror_kind: "coord.unknown_capability"rejects registration with unknown capability names. Operational metadata only — placement filter, not a security gate; the VM’s capability gateway remains the authority. -
Blob GC (sprint
W3-B). Newboruna evidence gc-blobscommand sweeps orphan content-addressed blobs from the data-dir’sblobs/tree (output blobs no longer referenced by any step checkpoint).--dry-runreports without deleting;--jsonemits a structured report. Closes the 0.5-S7 accepted limitation around manual blob cleanup. Library APIsBlobStore::find_orphans,BlobStore::delete, andRunCheckpointStore::all_referenced_blob_hashesare also exposed for future coord-side periodic-sweep wiring. -
boruna newinteractive scaffold (sprintW3-C). Wraps the existing template engine with stdin-driven prompting. Walks the user through template selection, target dir, and per-template variables; confirms before writing.--no-inputmode is CI-safe (errors on missing defaults rather than silently filling). Refuses to overwrite non-empty target dirs without--force. -
Coordinator HA / failover (sprint
W2). Multipleboruna coordinator serveprocesses can run against the same SQLite data-dir for active-active HA. Workers accept comma-separated URLs in--coordinatorand try them in order at registration time, sticking to the first reachable one. NewGET /api/healthendpoint returns{status, boruna_version, capability_set_hash, uptime_ms}and bypasses bearer auth so external load balancers can probe without holding the secret. Deployment topologies and failure-mode walkthroughs are documented atdocs/guides/coord-ha.md. -
Versioned workflow DAG schema (sprint
W4). Newschema_version: 1field required on everyworkflow.json. Spec atdocs/spec/workflow-dag-1.0.md.boruna_orchestrator::WORKFLOW_DAG_SCHEMA_VERSION = 1exposed for compatible readers. Forward-compat: 1.x readers accept any 1.y workflow (additive fields ignored). -
CI clippy gate now uses
--all-targets(sprintW1-A). All three clippy invocations in.github/workflows/ci.ymlnow include--all-targetsso test-code lint regressions surface at PR time instead of only at release-runner time. Filed as a followup in the B-2 retro after the workspace--all-targetssweep landed. -
Formal versioned
.axlanguage specification atdocs/spec/ax-language-1.0.md(sprintW1-B).language_version: "1.0"exposed viaboruna_compiler::LANGUAGE_VERSION. Newdocs/spec/README.mdindexes versioned specs. The narrative reference (docs/reference/ax-language.md) cross-links the spec. -
Versioned evidence bundle format with
format_version: "1.0"inbundle.json(sprintW1-C). Forward-compat reader gate rejects bundles from incompatible major versions; same-major bundles are accepted with unknown fields ignored. Spec:docs/spec/evidence-bundle-1.0.md.
Changed
- BREAKING: Evidence bundles now require a top-level
bundle.jsonmanifest (sprintW1-C). Legacy bundles from v0.5.0 and earlier must be migrated (migration tool planned for sprintW5-C; until then, re-record against a current binary). - BREAKING:
workflow.jsonfiles withoutschema_versionare now rejected (sprintW4). All bundled examples updated. Operator action: add"schema_version": 1to existing workflow definitions before upgrading.
Decided
- 1.x is the long-term-support line. At 1.0 GA, the surfaces listed
in
docs/lts.mdsection B are LTS-protected for the full 1.x line: every 1.0.axprogram, workflow.json, evidence bundle, MCP integration, and CLI invocation continues to work unchanged on every 1.y. Active support runs 18 months from 1.0 GA, security support 24 months. Breaking changes ride the 2.0 boat with at least 6 months of deprecation notice and migration tooling for any mechanically-derivable upgrade. Internal Rust APIs, default values, and logging output formats are explicitly out of scope — Boruna ships a CLI + binary, not a Rust library. Seedocs/lts.mdfor the full contract.
0.5.0 - 2026-04-28
Theme: distributed execution. Boruna can now run a fleet of
worker processes coordinated by a single HTTP coordinator,
drive workflows over the wire from CI runners that don’t share
a data-dir, handle large LLM step outputs without bloating the
SQLite store, and serve human-in-the-loop and webhook-driven
gates against a remote cluster. Read paths are consistent across
in-process resume, evidence-bundle creation, dashboard
rendering, and the step_input builtin — every persistence
reader of step outputs goes through the same accessor.
The 0.5-S2a → 0.5-S2f sub-sprint cycle landed during 0.4.x and
is included in this tag for the first time as a versioned
release (the distributed-execution stack: claim/lease
persistence, coordinator/worker HTTP MVP, lease-expiry sweep,
coord+dashboard listener-merge, workflow run --submit-only,
coordinator wait).
Added
-
Workspace clippy
--all-targetsis clean (sprintB-2). Pre-existing test-code lints from rustc 1.91+ in 4 crates (boruna-bytecode, boruna-vm, boruna-framework, boruna-compiler) plus a few in production paths cleared in one sweep. Auto-fix handledneedless_borrows_for_generic_args,manual_contains,clone_on_copy,for_kv_map,manual_is_multiple_of. Manual fixes formodule_inception(4× tests.rs files,#[allow]on inner mod),type_complexity(3 sites inllmvm/capability_gatewaytests, factored to aRecordedCallstype alias),approx_constant(test fixture used 3.14 for arbitrary roundtrip — replaced with 2.5),await_holding_lock(existing test had aMutexGuardwhose binding scope spanned an.awaitacross an explicitdrop(); rebound inside a block scope so drop is automatic before any await). -
Dashboard renders step outputs with blob-aware fallback (sprint
0.5-S7b). The per-run detail HTML page gains anOutputcolumn. Inline outputs render in a<code>block truncated to 256 chars; blob-stored outputs render[blob: <hash[..16]>…]linked to the S7/api/runs/{run_id}/blobs/{hash}route, without slurping the bytes into the dashboard render. Pending/Running/paused steps show—. Reads route throughRunCheckpointStore::read_step_outputfor inline cases (the same accessor used by the resume and evidence-bundle paths). The JSON detail endpoint (GET /api/runs/{id}) is unchanged —StepCheckpointalready serializes bothoutput_jsonandoutput_blob_reffields, so programmatic consumers can branch on the shape directly. 3 new HTML rendering tests. Seedocs/design-dashboard-blob-render.md. -
Output blob references for large step outputs (sprint
0.5-S7). Step outputs whose JSON encoding exceeds 64 KiB are now offloaded to a content-addressed blob store at<data-dir>/blobs/<aa>/<hash>, keyed by SHA-256. Thestep_checkpoints.output_blob_refcolumn carries the hash; the inlineoutput_jsoncolumn is left NULL when the blob path is used. Mutually exclusive: at most one of the two columns is populated for any terminal-state row. Audit hashes are unchanged — the ref IS the existingoutput_hash, so evidence-bundle replay across pre-S7 and post-S7 runs produces byte-identical hash chains. New schema migrationv3 → v4(additiveALTER TABLE ADD COLUMN, no table rewrite). New coordinator HTTP routeGET /api/runs/{run_id}/blobs/{hash}, bearer-gated and run-scoped (the route only serves bytes if the requested hash is referenced by a checkpoint under the given run_id, preventing the route from acting as a generic blob server). Newerror_kindtaxonomy:coord.blobs.bad_hash(400) andcoord.blobs.not_found(404). Threshold is hard-coded for the sprint (noPolicyknob); a future sprint may make it configurable. 27 new unit tests (15 blob_store, 12 persistence)- 5 new coord handler tests. See
docs/design-output-blob-refs.mdanddocs/architecture-output-blob-refs.md.
- 5 new coord handler tests. See
-
Distributed approval-gate / external-trigger (sprint
0.5-S6). Two new operator-facing routes —POST /api/runs/{run_id}/approveandPOST /api/runs/{run_id}/trigger— bearer-gated by the same auth middleware as worker endpoints. CLI flags--coordinator <url>+--coord-tokenadded toboruna workflow approve|reject|triggerso CI runners can drive remote runs without shareddata-dir. The wait driver (advance_run_one_tick) now opens approval / trigger gates when their dependencies complete, and closes them when the decision sentinel arrives inmetadata.approvals/metadata.triggers— same synthesized output shape as the in-process resume sentinel pass so a run approved via either route hashes to the same evidence bundle. Five handler unit tests + three advance-loop unit tests + one end-to-end CLI integration test. Newerror_kindtaxonomy entries:coord.approve.invalid_state,coord.approve.bad_payload,coord.trigger.invalid_state,coord.trigger.bad_token,coord.trigger.bad_payload. Seedocs/design-0.5-s6-distributed-approval-trigger.md. -
boruna workflow run --coordinator <url>(sprint0.5-S4). Submits a workflow over HTTP to a remote coordinator and polls for terminal status — eliminates the shared-data-dirrequirement for CI workflows. Workflow definition + every Source-kind step’s.axbody are inlined into the submit payload. Bearer token via--coord-tokenor theBORUNA_TOKENenv var. Exit codes matchcoordinator wait:0Completed,1Failed,2timeout / submit-failed. Two new coordinator HTTP routes:POST /api/runs/submitandGET /api/runs/{run_id}/status, both bearer-gated by the same auth middleware as worker endpoints. Status reads foldadvance_run_one_tickinto the request so the operator’s poll IS the wait driver. Six handler unit tests + three end-to-end CLI integration tests. Newerror_kindtaxonomy entries:coord.submit.invalid_workflow,coord.submit.bad_payload,coord.runs.not_found. Seedocs/design-0.5-s4-coordinator-flag.md. -
Sprint A debt cleanup (preceding commit
chore/0.5-debt-cleanup-2). Five carried-forward debts cleared in one pass: eliminateunsafe { env::set_var }(production CLI + 2 tests) by threading env name explicitly throughresolve_data_dir/metrics::export; newerror_class::TRANSIENT_NETWORKtaxonomy entry detected from bothVmError::AssertionFailedand wire-levelerror_msgstrings;AuditLog::from_entries_verifiedcalled at evidence-bundle creation to catch direct sqlite3 tamper ofmetadata.audit_log; new Prometheusboruna_workflow_run_duration_secondshistogram for p50/p95/p99 dashboards; five drift-detection tests fordocs/reference/policy.schema.json(caught real drift — schema was missingstep.inputcapability, fixed). -
Carried-debt cleanup pass (preceding session). Three small fixes from earlier-sprint adversarial-review findings that hadn’t been addressed:
-
Audit chain wait-driven terminating event. New
WorkflowRunner::append_wait_terminal_audit_eventemitsWorkflowCompletedto the audit chain when the wait driver reaches Completed or Failed terminal status. Idempotent — re-invoked waits don’t double-emit. Closes the gap from 0.5-S2f where submit-only emittedWorkflowStartedwith no terminating entry. 3 new unit tests. -
Two-concurrent-waits integration test. CORR-6 from 0.5-S2f adversarial review. Locks the design intent: two
coordinator waitprocesses against the same run_id both converge to exit 0 because the underlyinginsert_pending_step_if_absentandrequeue_failed_step_for_retryprimitives areINSERT … ON CONFLICT DO NOTHING(race-safe). -
Submit-only
--concurrencywarning. Adversarial finding F3 from 0.5-S2e.boruna workflow run --submit-onlysilently ignored--concurrencybecause parallelism in distributed mode is controlled by the worker pool, not the in-process wave loop. Now emits a clear stderr warning at submit time so operators know.
-
-
Path-resolution failure-mode prevention (this session). After the multi-sprint parallel-agent attempt that wrote files to the wrong worktree (because absolute paths in agent prompts bypass the
cwdredirection ofisolation: "worktree"), this session hardens the workflow:- New
docs/AGENT-PROMPT-TEMPLATE.md— reusable skeleton for parallel worktree-agent prompts. Bakes in the relative-path discipline + a worktree-verification block that agents must run before any file edit. - New
CLAUDE.md“Parallel-Agent Best Practices” section documenting the failure mode and the required prevention. - New project convention #31 — “Parallel worktree-agent prompts use RELATIVE paths only” — anchored in the convention memory.
- New project convention #32 — “Strong gates absorb tooling failures” — locks the recovery posture.
- New
-
Shared-secret bearer authentication for the coordinator (sprint
0.5-S3). Enables production deployment by gating every coord HTTP route on a bearer token.coordinator serve --shared-secret <hex>(orBORUNA_COORD_SECRETenv var) and worker--shared-secret <hex>(same env var fallback) configure the symmetric secret. Mismatched or missingAuthorization: Bearerheader returns401 + error_kind: coord.unauthorized. When unset, no auth is enforced — the pre-0.5-S3 loopback-only behavior is preserved for backwards compatibility, with a loud stderr warning when the coord binds to a non-loopback address without a secret.Generate a secret via
openssl rand -hex 32. mTLS, per-worker keys, and OAuth integration deferred to 0.6.x — shared-secret covers the common operator case (single trusted cluster, per-deployment secret rotation).Auth applies to merged dashboard routes too — operators who want a public read-only dashboard with auth-gated mutations should run a separate
boruna dashboard serveprocess. The coordinator’s merged listener is strictly all-or-nothing for auth.4 new CLI integration tests cover: missing bearer → 401, wrong bearer → 401, correct bearer → 200, no-secret legacy path → 200 (no regression).
-
Distributed retry policies (sprint
0.5-S5). Wires the existingRetryPolicy(max_attempts,on_transient,retry_on) through the wait driver so failed steps with retry budget transitionFailed → Pendinginstead of permanentFailed. The coordinator stays dumb — all retry-decision logic lives inWorkflowRunner::advance_run_one_tickand a new persistence primitiveRunCheckpointStore::requeue_failed_step_for_retry.The persistence primitive uses
BEGIN IMMEDIATE+ atomic status check inside the transaction; idempotent across concurrent wait clients (project convention §14). Returns a typedRequeueOutcome(Requeued { new_attempt_count },NotFailed { current_status },NotFound).AdvanceResultgains anewly_requeued: Vec<String>field (additive). Thecoordinator waitdriver prints a distinctstep <id>: requeued (retry)line for each requeued step before the generic transition print.Run-status derivation is updated:
Failedis declared only when a step isFailedAND has no retry budget remaining. AFailed-with-budget step keeps the runRunningand is requeued in the same tick.14 new orchestrator unit tests cover the retry pass: budget exhaustion, single-attempt rejection, error-class matching (
retry_onvs.on_transientfallback), concurrent-wait race (idempotency), and policy-absent short-circuit. The pre-0.5-S5 wait limitation (“distributed retry not honored”) is now resolved;docs/design-coord-wait.mdupdated accordingly. -
boruna fmtauto-formatter for.axfiles (DX sprint, first item from the 0.2.x DX lane). Canonical pretty-printer that walks the existing compiler AST and emits formatted source.CLI:
boruna fmt <file>rewrites in place;boruna fmt --check <file>exits 0 if the file is already formatted, exit 1 otherwise (CI gate). Exits 2 on parse errors so CI can distinguish “needs formatting” from “broken file”.Style decisions: 4-space indent, trailing comma on multi-line records and match arms, blank line between top-level decls, same-line opening braces.
Known limitation (v1): comments are stripped — the lexer drops them before they reach the parser, so the current AST has no comment positions. A token-aware comment-preserving formatter is future work. v1 is still useful as a CI gate for generated/scaffolded code or code reviews where comments are preserved manually.
3 golden-fixture tests, 1 idempotency roundtrip, 1 parse-failure error case, and 3 CLI integration tests (–check exit codes 0/1/2). New module
tooling/src/format/withformat_sourceandcheck_sourcepublic APIs. -
boruna coordinator wait <run-id>(sprint0.5-S2f). Multi-wave workflow advancement for distributed runs. Afterworkflow run --submit-onlywrites the first wave’s Pending checkpoints,coordinator waitpolls runs.db, computes downstream-ready successors as workers complete steps, and writes Pending checkpoints for the next wave — repeating until the run reaches a terminal status.boruna coordinator serve --data-dir /var/lib/boruna & boruna worker run --coordinator http://127.0.0.1:8090 & boruna workflow run examples/workflows/document_processing \ --submit-only --data-dir /var/lib/boruna # ↳ submitted run_id=... boruna coordinator wait <run-id> --data-dir /var/lib/boruna # ↳ polls every 500 ms, prints transitions per step, # exits 0 on Completed / 1 on FailedCoordinator gains zero new logic — the “dumb transport” invariant from 0.5-S2c is preserved. All wave advancement is client-side. The wait driver is stateless: kill it at any point and re-invoke; the run continues from the persisted state.
New flags on
coordinator wait:--poll-interval-ms <ms>(default 500, minimum 100; values below the floor are clamped with a warning).--max-wait-secs <s>(default 0 = unlimited; useful for CI timeouts).
Exit codes:
0Completed,1Failed,2error (run not found, missingworkflow_def, unsupported step kind in non- first wave),3--max-wait-secsexceeded.New persistence primitive
RunCheckpointStore::insert_pending_step_if_absent(run_id, step_id) -> boolusesINSERT ... ON CONFLICT DO NOTHINGso the wait client can safely write Pending checkpoints even when the coordinator is concurrently transitioning sibling steps. The legacyupsert_step_checkpoint(which hard-overwrites status on conflict) is unchanged; the new primitive is the race-safe variant for client-side advancement. Locked byinsert_pending_step_if_absent_preserves_running_row.New field
PersistedRunMetadata::workflow_def: Option<WorkflowDef>(with#[serde(default)]for back-compat). Embedded only whensubmit_only=true; capped at 1 MiB serialized JSON. In-process runs leave itNoneto keep metadata small.New
WorkflowRunner::compute_ready_steps(def, status_map)(pure, deterministic-sort) andadvance_run_one_tick(store, run_id) -> AdvanceResult(one polling tick).Tests: 14 new orchestrator unit tests covering the advance loop, the size cap, race-safe persistence, and idempotency. 4 new CLI integration tests: marquee multi-wave end-to-end, kill-and-resume, fail-on-bad-step, immediate-exit-on-already- completed.
Known limitations (deferred to 0.5-S3+):
- Retry policies in distributed mode — a step that fails is terminal; the wait driver exits with status 1 even if a retry policy would have succeeded in-process. Distributed retry is a future sprint.
- Audit-chain coverage — the wait driver does NOT append
WorkflowCompleted/WorkflowFailedevents. Submit-only emitsWorkflowStartedbut the chain has no terminating entry for distributed runs. Auditors should check persistedruns.statusdirectly. - Concurrent wait clients — multiple
coordinator waitprocesses against the samerun_idare safe (the race- safe primitive ensures idempotency) but not specifically tested as an integration scenario. - HTTP-based remote wait — the wait client requires
filesystem access to
--data-dir. A truly remotecoordinator wait --coordinator <url>mode is a future sprint.
-
boruna workflow run --submit-only(sprint0.5-S2e). The first end-to-end path for dispatching a real workflow through a coord+workers cluster. Submit-only mode: validates + computes the DAG, embeds source-step bodies inmetadata_json.step_sources, inserts the run row + initial wave’s source-step Pending checkpoints, then exits before spawning thread workers. The cluster picks up the steps via existing claim/dispatch mechanisms.boruna coordinator serve --data-dir /var/lib/boruna & boruna worker run --coordinator http://127.0.0.1:8090 & boruna workflow run examples/workflows/llm_code_review \ --submit-only --data-dir /var/lib/borunaWorkflows using approval-gate / external-trigger steps in the first wave are rejected at submit time with a typed error (
submit-only mode does not support ... in the first wave). Distributed mode for those features is deferred.Multi-wave automatic advancement is NOT done — operators monitor via the dashboard or
boruna workflow show <run-id>. Wave loop integration becomes 0.5-S2f or later.Added field
RunOptions::submit_only: booland fieldPersistedRunMetadata::step_sources: BTreeMap<String, String>(with#[serde(default)]for back-compat). TheWorkflowStartedaudit event fires for submit-only runs matching the in-processrun_persistentsemantics.Tests: 3 new unit tests (insertion shape, metadata embedding, approval-gate rejection) + 1 new CLI integration test that runs
boruna workflow run --submit-onlyagainst a real workflow.json + .ax file with a spawned coord+worker pair, asserts the step transitions through Pending → Running → Completed and the output_json matches the expected value. -
Coordinator + dashboard listener-merge (sprint
0.5-S2d). The dashboard’s read-only routes (/,/runs/:id,/api/runs,/api/runs/:id) are now served on the same listener as the coordinator’s worker routes (/api/workers/...,/api/work/...). Operators get fleet visibility AND distributed dispatch from a singleboruna coordinator serveinvocation — one process, one port, one connection to runs.db.The merge is automatic — anyone running the coordinator gets the dashboard routes too. The standalone
boruna dashboard servekeeps working unchanged for read-only deployments without the coordinator overhead.The coordinator’s
bind_warningflows into the dashboard builder so the red HTML banner correctly fires when the coordinator is bound to a non-loopback address. Operators can’t accidentally expose the coordinator without the dashboard banner warning them.Refactor:
dashboard::dashboard_routes(store, bind_warning)is now apubroute builder taking primitive args. The coordinator merges it onto its own router. Zero copy-paste; both the standalone dashboard and the coordinator use the same builder.3 new CLI integration tests cover the merged surface.
-
Coordinator background lease-expiry sweep (sprint
0.5-S2c). The coordinator now runs a tokio interval task that wakes up every--sweep-interval-ms(default 30 s) and callsexpire_leases_and_requeue. Stale leases from worker crashes are now recovered without restarting the coordinator. Best-effort failure semantics: errors log + continue to the next tick.New CLI flag on
boruna coordinator serve:--sweep-interval-ms <ms>(default 30000, minimum 100; values below the floor are clamped with a warning).New CLI integration tests:
coord_bg_sweep_requeues_expired_leaseproves the sweep fires periodically and requeues stale leases without a coordinator restart.worker_completes_two_step_linear_dagproves the protocol scales beyond a single step. (DAG advancement by the coordinator itself is deferred to 0.5-S2d; this test pre-populates both steps as Pending up front.)
Architectural note documented in
docs/design-coord-bg-sweep.md: in v0.5.x the coordinator is a “dumb transport” — it dispatches what’s in Pending and persists what completes. Wave advancement (deciding which step is Pending after a successful completion based on DAG dependencies) lives in the client. Theboruna workflow run --coordinator <url>client mode ships in 0.5-S2d. -
Coordinator/worker HTTP MVP (sprint
0.5-S2b). The HTTP layer over the persistence-layer state machine from 0.5-S2a. Two new CLI subcommands behind theservefeature flag:boruna coordinator serve --data-dir <path> [--port 8090] [--bind 127.0.0.1] [--max-lease-ttl-ms 300000] [--poll-timeout-ms 30000]boruna worker run --coordinator <url> [--worker-id <name>] [--lease-ttl-ms 300000] [--poll-timeout-ms 30000]
Six HTTP routes per ADR 002:
POST /api/workers/register,POST /api/workers/heartbeat,GET /api/work/claim(long-poll),POST /api/work/complete,POST /api/work/fail,POST /api/work/extend-lease. Every response carriesprotocol_version: 1. Worker-side: register → long-poll claim → compile + execute the step’s.axsource → POST result. Heartbeats every 10 s in a background task.Stable
coord.*error_kindtaxonomy, locked at this sprint’s ship:coord.lease_expired,coord.unknown_worker,coord.binary_mismatch,coord.invalid_request,coord.output_too_large,coord.step_not_found. The HTTP layer maps the persistence-layer outcome enums (from 0.5-S2a) 1:1 — no string-equality drift.Workers must match the coordinator’s
capability_set_hashper ADR 002’s atomic-upgrade rule; mismatched workers get409 + coord.binary_mismatch. Output payload size capped at 8 MiB per ADR 002; oversize bodies get413 Payload Too Largefrom Axum’sDefaultBodyLimit.Workers parse policy via the strict validator from sprint
0.4-S15(boruna_vm::policy_validate::parse), so workers reject the same shapes the CLI rejects with the same stableerror_kindstrings.On startup, the coordinator runs
expire_leases_and_requeueto void any stale leases left over from a prior coordinator process (per ADR 002’s “coordinator restart = all leases void” rule).Loopback (
127.0.0.1) by default. Non-loopback bind emits a loud stderr warning. No authentication — operators exposing the coordinator MUST front it with an auth-enforcing reverse proxy.Tests: 9 coordinator handler unit tests (route shapes, error_kind strings, status codes, lease-cap enforcement) + 4 worker unit tests (compile, execute, hash determinism, url-encoding) + 6 CLI integration tests including the flagship
worker_kill_mid_step_lease_expires_then_reclaimregression that exercises the slow-but-not-dead worker race end-to-end at the wire level.New deps in the
servefeature:reqwest 0.12(json + rustls-tls, no openssl) for the worker’s HTTP client;uuid 1for worker_id / session_token allocation.Not in this sprint (deferred to 0.5-S2c): workflow runner integration (
boruna workflow run --coordinator <url>), wave-loop coordinator-side dispatcher, dashboard + coordinator listener-merge.See
docs/design-coordinator-worker-http.md,docs/architecture-coordinator-worker-http.md,docs/test-plan-coordinator-worker-http.md. -
Claim/lease persistence API (sprint
0.5-S2a). The persistence-layer half of ADR 002. Schema v3 adds three operational columns tostep_checkpoints:worker_id(opaque worker handle),lease_expires_at(unix ms), andclaim_id(monotonic per(run_id, step_id), CAS key for terminal-state transitions). Five new methods onRunCheckpointStore:claim_step— atomic Pending → Running transition with incrementedclaim_id.complete_step_cas— CAS-protected completion. Rejects late writes from expired-lease workers without changing persisted state.fail_step_cas— CAS-protected terminal failure.expire_leases_and_requeue— sweep expired leases back to Pending. Idempotent.extend_lease_cas— push out the lease deadline, CAS-protected against the originalclaim_id.
-
New outcome enums:
ClaimOutcome,TerminalOutcome,ExtendOutcome. Each carries a stablekind() -> &'static strper project convention #2 (claim.*,terminal.*,extend.*). These map to the wire-levelcoord.*error_kindstrings the HTTP coordinator will lock in 0.5-S2b. -
Schema v2 → v3 migration via the existing migration runner. Idempotent — re-opens are no-ops; fresh databases get the full v3 schema directly from
SCHEMA_V1_SQL. -
32 new persistence tests including the load-bearing
slow_worker_race_late_completion_rejectedregression that exercises the slow-but-not-dead worker race the ADR’s adversarial review caught: claim → expire → reclaim → original worker’s late completion →LeaseExpiredrejection → row state unchanged. If this test ever fails, the state machine is broken. -
The single-process
WorkflowRunnerpath is unchanged.upsert_step_checkpointdoes not write the new columns; they stay at their defaults (None,0) for steps that flow through the in-process scheduler.
Decided
- ADR 002 — Distributed step execution. The 0.5.0 (“Scale”)
cycle’s foundational architectural decision. Distributed mode
uses an embedded HTTP coordinator + lightweight HTTP workers,
all behind the existing
servefeature flag. The coordinator remains the only writer ofruns.db; workers long-poll for claimable steps and report results via JSON over HTTP. Lease- based claim with re-dispatch on expiry handles worker crashes. Determinism is preserved: which worker ran a step is operational state and never enters the audit/replay pipeline. The single-process path (boruna workflow run/resume) keeps working unchanged. Considered alternatives — shared-filesystem SQLite, external queue (Redis/RMQ/SQS), gRPC — were rejected for footgun risk, deployment-simplicity violation, and marginal benefit respectively. Implementation in0.5-S2. Seedocs/adr/002-distributed-step-execution.md.
0.4.0 — 2026-04-27
The operations release. Twelve sprints (0.4-S5 through 0.4-S16) ship the production-readiness layer on top of 0.3.0’s durability work: distributed-tracing observability, streaming progress, multi-pause-per-level wave loops, per-error-class retry classification, hash-chained audit decisions and lifecycle events, post-hoc evidence-bundle creation, Prometheus metrics, multi-provider LLM dispatch, multi-environment data separation, strict-validated policy-as-code, and a read-only HTTP dashboard.
Added
-
Workflow dashboard (sprint
0.4-S16). Newboruna dashboard servesubcommand exposes a read-only HTTP view ofruns.dbso operators can triage at a glance without dropping intosqlite3. Loopback (127.0.0.1) by default;--bind 0.0.0.0is allowed but shouts a loud warning on stderr AND renders a red banner in the HTML, because the dashboard ships with no authentication.cargo build --release -p boruna-cli --features serve boruna dashboard serve --data-dir /var/lib/borunaRoutes:
GET /(HTML index),GET /runs/:id(HTML detail),GET /api/runs(JSON list),GET /api/runs/:id(JSON detail). Zero mutation routes —POST/PUT/DELETE/PATCHto any path return 405. Multi-env aware: when--envis set, the dashboard reads<data-dir>/<env>/runs.dbper the 0.4-S14 contract.Builds behind the existing
servefeature flag (already used byboruna servefor framework apps). Reuses the workspaceaxum 0.8+tokiodeps. -
boruna_orchestrator::persistence::{RunRow, RunRecord, RunOperational, StepCheckpoint}now deriveSerializeso read-only consumers can render rows directly. (NotDeserialize— there’s no scenario where a dashboard consumer should be reconstructing a row.) -
18 new unit tests in
dashboard::testscovering every handler, HTML escaping (XSS regression), bind-warning banner, 404, and the date-format helper. 8 new CLI integration tests incrates/llmvm-cli/tests/cli_dashboard.rscovering end-to-end HTTP behavior, the read-only contract (POST → 405), and CLI error paths (missing data-dir, invalid bind address). -
New CI steps to build and test the
servefeature (cargo build/test/clippy -p boruna-cli --features serve). -
New reference doc
docs/reference/dashboard.mdcovering build, run, security posture, routes, stability tier. -
Policy management as code (sprint
0.4-S15). Operators now treat--policyfiles as versioned, validated, code-reviewable artifacts. Two new CLI subcommands:boruna policy validate <file> [--json]— strict-validate a policy file. Designed as a CI gate. Exits 0 on ok, 2 on validation error, 1 on file IO error.boruna policy show <file>— validate then print the effective policy (default behavior, denormalized rule list, net_policy bounds). Plus a new MCP toolboruna_policy_validate(policy_json)that runs the same validator. The CLI, MCP, andboruna run --policy <file>paths now share one parser — passing validate but failing run is structurally impossible.
-
New
boruna_vm::policy_validate::{parse, parse_file, PolicyParseError, POLICY_SCHEMA_VERSION}. The validator enforces:schema_version∈ {1} (other values rejected — locks the contract for forwards-compat).- Top-level /
net_policy/ per-rule fields are an allow-list — unknown fields rejected (policy.unknown_field). Closes the silent-default footgun where"default_alow": trueparsed asdefault_allow: false. ruleskeys must be canonical capability names. Aliases ("net","db", …) rejected with a hint to the canonical name ("net.fetch","db.query"). Aliases used to silently no-op at gateway-check time.net_policy.max_response_bytes > 0,timeout_ms > 0,allowed_methods⊆{GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS}(canonical upper-case; lower-case rejected).
-
Stable
error_kindtaxonomy — locked per project convention #2:policy.io_error,policy.parse_error,policy.unknown_schema_version,policy.unknown_field,policy.invalid_capability,policy.invalid_net_policy. Future validators can add new kinds; existing kinds never rename. -
26 unit tests in
boruna_vm::policy_validate+ 11 CLI integration tests incrates/llmvm-cli/tests/cli_policy.rs+ 7 MCP tests + 3 protocol_version regression tests. -
Updated
docs/reference/policy-schema.mdwith the strict-validator rules, error_kind taxonomy, and CLI tooling examples. Design rationale indocs/design-policy-as-code.md.
Fixed
-
Policy::default()now producesschema_version: 1(matching what the lenient deserializer’s#[serde(default = "...")]produces for an empty input). The derived default leakedschema_version: 0into round-trips — invisible until the0.4-S15strict validator surfaced it. AffectsPolicy::deny_all()and any caller that started fromPolicy::default(). -
Multi-environment support (sprint
0.4-S14). New global--env <name>flag (also fromBORUNA_ENVenv var). When set:--data-diris namespaced to<data-dir>/<env>/so each environment has its own runs.db, audit chains, and evidence bundles.- Every Prometheus metric gains an
env="<env>"label so dashboards can filter / group by environment.
boruna --env staging workflow run wf --data-dir /var/lib/boruna ... boruna --env prod workflow run wf --data-dir /var/lib/boruna ... # → /var/lib/boruna/staging/ and /var/lib/boruna/prod/ stay separateOperators get dev/staging/prod separation without external orchestration. Per-env policy is supplied via
--policyper call. -
New
boruna_orchestrator::metrics::format_prometheus_with_envvariant. Backward compatible:format_prometheus(snap)continues to produce env-less output (callsformat_prometheus_with_env(snap, None)internally). -
New CLI helper
validate_env_namerejects names with characters outside[a-zA-Z0-9_-](length 1-64). Protects against path traversal (--env ../../etc/passwdis rejected at the boundary) and broken Prometheus labels. -
4 new tests in
metrics: env label added to every series, env-less output is byte-identical to legacy, env label escapes, end-to-endBORUNA_ENVround-trip via theexportentry.
Backward compatibility
When --env and BORUNA_ENV are both unset, behavior is exactly
as before: data goes to <data-dir>/, metrics carry no env label.
Operators upgrading from 0.4-S13 see no change unless they opt in.
-
LlmRouterHandler— multi-provider LLM dispatch helper (sprint0.4-S13). Direct extension of the BYOH decision in0.3-S8. Integrators with multiple LLM providers (OpenAI + Anthropic + local Ollama / vLLM) no longer need to write their own dispatch logic — the router takes a registry of provider handlers and routes eachCapability::LlmCallbased on aprovider/modelprefix inargs[1]:#![allow(unused)] fn main() { let mut providers: BTreeMap<String, Box<dyn CapabilityHandler>> = BTreeMap::new(); providers.insert("openai".into(), Box::new(my_openai_handler)); providers.insert("anthropic".into(), Box::new(my_anthropic_handler)); let router = LlmRouterHandler::new(providers, Box::new(MockHandler)); }.axcallers then writellm_call("Summarize:", "openai/gpt-4")— the prefix selects the provider; the full model string (including the prefix) is forwarded unchanged so providers can use it for internal tagging. -
The router is pure routing logic — Boruna still ships zero provider HTTP code. Each provider’s handler implementation, authentication, and response parsing belong to the integrator per the BYOH model.
-
Non-LLM capability calls pass through to a fallback handler so the router composes with the existing
StepInputHandler/MockHandler/HttpHandlerstack. -
Typed errors for: missing model arg, non-string model arg, malformed model string (no
/), empty provider prefix, unknown provider (error message includes the registered providers list). -
Late-registration support via
add_provider(name, handler)returning the previously-registered handler. -
11 unit tests covering routing, args forwarding, error variants, fallback delegation, late registration, and deterministic
registered_providersordering. -
Updated
docs/guides/llm-integration.mdwith a new section walking through the router setup. -
Prometheus metrics export CLI (sprint
0.4-S12). Newboruna metrics export --data-dir <DIR>command reads the persistent run store and writes Prometheus text format to stdout. Operators integrate via cron +node_exporter’s textfile collector — the canonical Prometheus pattern for batch tools:*/30 * * * * boruna metrics export --data-dir /var/lib/boruna \ > /var/lib/node_exporter/textfile_collector/boruna.promArchitectural decision documented in
docs/design-prometheus-metrics.md: CLI-pulled (not embedded HTTP) to align with Boruna’s CLI-only philosophy locked in0.3-S15(BYOH webhook pattern). No new long-running daemon process. -
Three metric families:
boruna_workflow_runs_total{workflow,status}— counter of runs by terminal/transient status.boruna_workflow_runs_in_flight{workflow}— gauge ofrunningorpausedruns.boruna_workflow_step_completions_total{workflow,step,status}— counter of step terminal transitions (completed/failed).
-
New
boruna_orchestrator::metricsmodule withcompute_snapshot,format_prometheus, andexportpublic entries. The snapshot is pure data so future exporters (JSON dashboard endpoint, etc.) can reuse it without re-querying the store. -
8 unit tests covering: empty store emits HELP+TYPE only, aggregation by workflow/status, in-flight counting, terminal step transitions only (no Pending/Running noise), output is valid Prometheus textfile format with HELP/TYPE preceding data, determinism (BTreeMap iteration locked), label escaping (backslashes, quotes, newlines per the exposition spec), end-to-end realistic run set.
Counter semantics caveat
Counters are computed from current store state at sample time, not
maintained as deltas. If old runs are pruned from the DB, the
_total will decrease — Prometheus normally treats this as a
counter reset and handles it gracefully via rate(). Operators
running frequent pruning should be aware of this contract.
- Full lifecycle audit events (sprint
0.4-S11). Closes the audit theme for 0.4.0. The audit chain now captures the complete run lifecycle, not just operator decisions:WorkflowStarted { workflow_hash, policy_hash }— appended atexecute_after_insert’s top, immediately after the run row inserts.StepCompleted { step_id, output_hash, duration_ms }— appended after each step’s terminalCompletedcheckpoint write.StepFailed { step_id, error }— appended after each step’s terminalFailedcheckpoint write (including panic-failed workers in the concurrent path).WorkflowCompleted { result_hash, total_duration_ms }— appended at terminal status only (Completed/Failed). Resume’s terminal exit also appends it. Pause states leave the chain open for the next resume to extend.
- New
append_audit_event(store, run_id, event)helper using the same CAS-retry pattern asrecord_approval_decision/record_external_trigger. Lifecycle appends are best-effort: a CAS budget exhaustion logs a warning and continues. Missed audit events are operationally annoying (chain has fewer step events than checkpoints) but never fail the run — the chain entries that DID commit remain valid, and an auditor atverifytime sees the gap explicitly. StepStartedevents are deliberately NOT emitted — the checkpoint’sstarted_at_msalready captures per-step start operationally, and emitting an event-per-start would double the CAS-write count for limited compliance value.- 2 new tests in
tests::evidence_bundle:lifecycle_events_emitted_in_order_for_multi_step_run(4-entry chain in topological order: Started → 2× StepCompleted → Completed) andstep_failed_event_emitted_on_runtime_error(chain captures the failed step + error message). - 7 existing audit_decisions / evidence_bundle tests updated to match the new chain shape (lifecycle events + decisions).
Audit theme summary
Across 0.4-S9 (decisions), 0.4-S10 (bundle creation), and
0.4-S11 (lifecycle events), the audit story is now end-to-end
complete: every persistent run produces a hash-chained audit log
of all lifecycle transitions and operator actions, the chain is
persisted atomically with the corresponding state changes, and
boruna evidence create <run-id> packages it with all reproducibility
artifacts for downstream verification via boruna evidence verify.
Performance impact
For a workflow with N steps, the chain now requires roughly N+2 additional CAS-protected metadata writes (1 WorkflowStarted, N StepCompleted/Failed, 1 WorkflowCompleted). Each write is a single SQLite UPDATE with a small JSON blob. For typical workflows this is operationally negligible. High-throughput integrators can disable lifecycle audit by deferring this sprint’s wiring (no disable flag ships in this sprint — file an issue if needed).
boruna evidence create <run-id>(sprint0.4-S10). Builds an evidence bundle from a persisted run by reading the run’s metadata, step checkpoints, and hash-chained audit log. Closes the audit-evidence loop end-to-end:$ boruna workflow run wf --data-dir .data --policy allow-all $ boruna workflow approve <run-id> <step-id> --data-dir .data $ boruna workflow resume <run-id> --data-dir .data $ boruna evidence create <run-id> --output-dir bundles --data-dir .data $ boruna evidence verify bundles/<run-id> # VALID- New
boruna_orchestrator::workflow::create_bundle(data_dir, run_id, output_dir)public entry. Reads workflow.json from the run’s recordedworkflow_dir, policy from the persistedpolicy_jsoncolumn, per-step outputs fromstep_checkpoints.output_json, and the full audit chain frommetadata.audit_log(sprint 0.4-S9). Builds anEvidenceBundleBuilder, finalizes, returns theBundleManifest. - 6 new tests in
tests::evidence_bundle: complete artifact for a completed run, audit chain round-trip via JSON, end-to-endverify_bundle()passes on the produced bundle, trigger payload hash matches the synthesized step output_hash, unknown run id returns typedRunNotFound, runs without decisions produce an empty chain whoseaudit_log_hashis the all-zeros sentinel.
Post-hoc bundle creation
The runner does NOT auto-create bundles during execution — the hot path stays free of bundle I/O. Operators trigger bundle creation explicitly when needed (e.g., a compliance request months after the run completed). Same model as the rest of the audit subsystem: operator-driven, not runner-driven.
- Audit-log integration of approval / trigger decisions (sprint
0.4-S9). Closes a 0.3.0 carried-forward debt. Operator actions (approval grants/denials, external trigger events) now produce hash-chained audit entries, persisted asmetadata.audit_logand written atomically with the operator-facing decision via the existing CAS-protected metadata writes. - New
AuditEvent::ExternalTriggerReceived { step_id, payload_hash }variant. Thepayload_hashmatches the synthesized stepoutput_hash(since the trigger payload becomes the step’s output value), so the chain links to the replay-verified output. Payload itself is hashed rather than logged verbatim — webhook bodies may contain operator PII. - New
AuditLog::from_entries(Vec<AuditEntry>) -> SelfandAuditLog::into_entries(self) -> Vec<AuditEntry>for round- tripping the chain through a containing struct (e.g. the run’s persisted metadata) without re-serializing to JSON. - 7 new tests covering: approval-grant / approval-reject append the right event, trigger appends with payload_hash equal to output_hash, multi-decision chain integrity (prev_hash chains), legacy 0.3.x metadata round-trip without audit_log field, audit log persists unchanged across resume, first decision after legacy metadata starts a fresh genesis chain.
- Design doc:
docs/design-audit-decision-events.md.
Tamper-evidence vs replay-verification
The audit chain’s prev_hash linkage is tamper-evident — any
post-hoc mutation (direct sqlite3 surgery, bit-flip in storage)
surfaces when an auditor calls AuditLog::verify(). The chain is
not processed by the run’s deterministic-execution replay
pipeline; replay verifies per-step output_hash, not the
operator-action chain. Documented prominently in the
PersistedRunMetadata.audit_log doc-comment to prevent confusion
with the replay-verification subsystem.
Backward compatibility
A 0.3.x metadata blob with no audit_log field deserializes via
#[serde(default)] to Vec::new(). The first decision recorded
by a 0.4-S9 binary on a 0.3.x run starts a fresh genesis chain
(sequence=0, prev_hash=“0”*64). Locked by
first_decision_after_legacy_metadata_starts_chain_at_sequence_zero.
What this sprint does NOT ship
-
Full lifecycle audit events (
WorkflowStarted,StepStarted,StepCompleted, etc.) — separately scheduled. This sprint surgically closes the operator-action audit gap without touching the per-step hot path. -
Audit log in evidence bundles —
EvidenceBundleBuilder::finalizealready accepts anAuditLogparameter; wiring the in-metadata log into bundle construction is a small follow-on sprint. -
Operator identity capture — no auth subsystem yet. The
approverfield is empty string until a future identity sprint wires real auth. The field IS captured in the hash chain regardless so a future upgrade can fill it in without re-keying past entries. -
Per-error-class retry classification (sprint
0.4-S8). TheRetryPolicyschema gains an explicitretry_on: Vec<String>allowlist alongside the legacy binaryon_transientgate. Operators who want “retry on transient timeouts but NOT on auth errors or bad code” now express it directly:"retry": { "max_attempts": 3, "on_transient": false, "retry_on": ["wall_time_exceeded", "io_error"] } -
New
error_classtaxonomy with stable string constants:wall_time_exceeded,step_limit_exceeded,capability_denied,capability_budget_exceeded,compile_error,runtime_error,io_error,input_resolution. Forward-compatible — new classes add without breaking existing policies. -
New
classify_vm_error(&VmError) -> &'static strmaps every VM error variant to its taxonomy class. Catch-all isruntime_error(assertions, type errors, OOB, divisions, stack errors, bytecode errors all surface here). -
should_retry_class(policy, class) -> bool— central decision function. Resolution order: no policy / max_attempts ≤ 1 → false; non-emptyretry_on→ match in list; empty → fall back toon_transient. -
retry_with_backoffshort-circuits on non-retry-eligible failures rather than running through the full backoff schedule. A compile error no longer waits 100+200+400ms before giving up. -
17 new tests covering classification mappings, allowlist semantics, legacy fallback, unknown-class-ignored, retry_on takes precedence over on_transient=false, and serde round-trip for legacy 0.3.x workflow.json files (no
retry_onfield).
Backward compatibility
-
A 0.3.x
workflow.jsonwithretry: {max_attempts, on_transient}(noretry_onfield) deserializes withretry_on = vec![]via#[serde(default)]. The empty allowlist falls back to the legacyon_transientgate, so prior behavior is exactly preserved. -
classstrings are case-sensitive. Use the lowercase snake_case forms documented inerror_class::*. Unknown strings (typos like"transient_netwrok") are silently ignored — they never match a real failure class, so the policy behaves as if the typo were absent (conservative-by-default). -
Wave-loop multi-pause-per-level (sprint
0.4-S7). The concurrent execution path (--concurrency >= 2) now pauses ALL pause-steps in the same DAG level in a single execution pass — previously only the first was processed and remaining pauses were silently deferred to subsequent resumes. Enables “wait for payment AND fraud-check” webhook fan-in patterns where multipleexternal_trigger(orapproval_gate) steps depend on a shared upstream and a downstream step depends on all of them. Each pause persists its own checkpoint and (for trigger steps) mints its own distinct token. The resume sentinel pass advances each pause independently as its decision/event arrives. -
New
persist_one_pausehelper isolates per-pause persistence errors. If one pause’sacquire_trigger_tokenorupsert_step_checkpointfails (transient/dev/urandomerror, CAS retry exhaustion, disk error), the loop logs a warning and continues to the next pause. The run is markedPausedon the pauses that DID commit, leaving operators with a recoverable state. The next resume’s wave loop is idempotent —acquire_trigger_tokenreuses existing tokens andupsert_step_checkpointis re-write-safe — so the failed pauses retry cleanly. Reviewed in 0.4-S7 — earlier draft propagated the first per-pause error, terminally-failing the run and stranding pause #1’s token with no recovery path. -
5 new tests in
tests::multi_pause_per_wave: 2-trigger parallel pause, partial trigger fire keeps other paused, full trigger fire advances downstream, mixed approval+trigger pauses, partial-pause failure recovery via direct-SQL state injection.
Asymmetry note
The sequential execution path (--concurrency 1) is unchanged: it
processes one step at a time and serializes parallel pauses across
multiple resumes. Operators expecting AND-fan-in webhook patterns
must use --concurrency 2 or higher.
- Streaming progress notifications from
boruna_run(sprint0.4-S6, closes #4). When the MCP caller supplies aprogressTokenin the request_metafield (per the MCP spec), the server emitsnotifications/progressevents with the cumulative VM step count every 100k opcodes. Long-running scripts no longer block the calling agent’s UI behind a single final result blob. Backward compatible: callers without a progressToken see the legacy synchronous behavior unchanged. - New
Vm::start_timer()method — initializes the wall-clock timer used bymax_wall_msbudgets. Callers driving the VM throughexecute_boundedshould call it beforeset_entry_functionto matchVm::run’s timing contract (the entry-frame allocation counts toward the budget). - New
Vm::set_in_actor_context(bool)flag — replaces the priorbudget.is_some()heuristic for distinguishing actor-system scheduling from standalone bounded execution.Op::ReceiveMsgon an empty mailbox now blocks (rewind IP +MailboxEmpty) only when the flag is set; standalone bounded loops fall through withValue::Unit, matchingVm::run’s legacy semantics. Reviewed in 0.4-S6 — without this fix, the streaming-progress and non-streaming paths ofboruna_runwould diverge for any program emittingOp::ReceiveMsgoutside an actor system. ActorSystem::runsetsin_actor_context = trueon the root and every spawned child VM.
0.3.0 — 2026-04-26
Theme: Real-use durability. 0.3.0 makes Boruna usable for
long-running, durable, production workflows. Persistent state survives
process restarts; concurrent steps fan out within waves; transient
failures retry with backoff; webhook-driven steps wait for external
events. The full sprint stack (0.3-S2a through 0.3-S16) closes
every big-rock theme on the original 0.3.0 plan and adds review-
driven safety work.
Added
- Persistent workflow state (sprints
0.3-S2a/S2b/S3/S6). Crash-resumable runs via SQLite-backed checkpoint store withBEGIN IMMEDIATEatomicity,f_FULLFSYNCon macOS for durability, and a--data-dirflag onboruna workflow run/resume. - Approval-gate operator UX (sprint
0.3-S2c). Newkind: "approval_gate"step type pauses the run; operators advance viaboruna workflow approve <run-id> <step-id>/boruna workflow rejectwith optional reason. Decisions persisted in run metadata. - Concurrent step execution within waves (sprint
0.3-S4).--concurrency Nonrun/resumeparallelizes steps at the same DAG topological level. Determinism preserved: sameoutput_hashregardless of concurrency. - Step retry policies (sprint
0.3-S5). Configurable per-step retry with exponential backoff (100ms × 2^N capped at 5s) for transient failures. - Idempotent invocation (sprints
0.3-S7+0.3-S10).--skip-if-runningflag for cron-driven scheduling. Atomic skip-if-in-flight check + insert in a single transaction closes the prior race window. - LLM handler decision: Bring Your Own Handler (sprint
0.3-S8). No default LLM handler ships in core; integrators wire their provider via theCapabilityHandlertrait. Reference OpenAI handler + integration contract indocs/guides/llm-integration.md. - Workflow versioning for CI/CD safety (sprint
0.3-S9).--expect-workflow-hashflag refuses runs / resumes when the on-disk definition’s hash doesn’t match. - Per-step
attempt_countcolumn (sprints0.3-S11/S12/S13) with the project’s first schema migration (v1→v2) viacolumn_exists+if v < Npattern.boruna workflow showsurfaces the column. Sequential failure path persists actual count. - Workflow step output piping via
step_inputbuiltin (sprint0.3-S14).let received: String = step_input("name")returns the JSON-encoded upstream output. NewCapability::StepInput(id=10). Both sequential and concurrent paths resolve inputs coordinator- side. Unknown input names error with the declared list (review- driven). - Async step execution via external trigger CLI (sprint
0.3-S15). Newexternal_triggerstep kind for webhook-driven workflows.boruna workflow trigger <run-id> <step-id> --token <X> --payload <json>records the payload as the step’s output value. 32-hex-char tokens from/dev/urandom(no fallback) prevent accidental cross-step triggers. Constant-time validation; webhook- replay rejected byStepAlreadyTriggered. Boruna stays a CLI tool — no in-binary HTTP server. - Real HTTP handler with SSRF protection (added Feb 2026).
Feature-gated
httpbuilds enable real network calls via--live.NetPolicyallowed_domains / methods / byte limits / timeout. Rejects private IPs, localhost, non-http schemes. - 23 new typed errors covering approval-gate, trigger-gate, run-not- resumable, step-not-found, hash-mismatch, and CAS-budget-exhausted states.
Fixed
- Trigger-flow TOCTOU race (sprint
0.3-S16). The 0.3-S15 trigger flow split metadata writes (CAS) and step-checkpoint transitions (resume sentinel pass) across two separate SQL transactions. A concurrentboruna workflow resumecallingmark_step_running_clearing_outputbetween the trigger function’s metadata-CAS and the next resume’s sentinel pass could leave the payload silently logged-and-discarded. Fixed by wrapping the metadata CAS and the checkpoint transition in a singleBEGIN IMMEDIATESQL transaction (newRunCheckpointStore::commit_external_trigger). SQLite’s write-locked transaction blocks concurrent writers, making the checkpoint state read inside the transaction authoritative. - New
TriggerCommitOutcomeenum (Committed | MetadataChanged | CheckpointStateMismatch { current_status }) for callers that need to distinguish CAS-retry-eligible races from operator-error states. - Resume sentinel pass remains in place as a defensive recovery for
legacy 0.3-S15-format DBs (metadata.triggers populated with non-empty
payload but checkpoint still in
awaiting_external_event). New forward-compat test confirms the upgrade path. - 5 new persistence-layer unit tests + 3 new runner-level integration tests cover the atomic-commit outcomes and the legacy upgrade scenario.
Added
- Async step execution via external trigger CLI (sprint
0.3-S15). Newexternal_triggerstep kind for webhook-driven workflows. The runner pauses at the gate; an operator (or webhook receiver) advances it withboruna workflow trigger <run-id> <step-id> --token <X> --payload <json>, and the payload becomes the step’s output value (visible to downstream steps viastep_input).
Pause-time prints a 32-hex-char trigger token (16 bytes from"webhook": { "kind": "external_trigger", "description": "Stripe payment.succeeded webhook", "depends_on": ["init"] }/dev/urandom); the CLI rejects mismatching tokens to prevent accidental cross-step triggers from a misrouted webhook. Boruna stays a CLI tool — no in-binary HTTP server. The operator’s webhook receiver bridges to the CLI. - New
StepKind::ExternalTrigger { description }variant on workflow step definitions; newStepStatus::AwaitingExternalEvent(persisted as"awaiting_external_event"). - Public entry
boruna_orchestrator::workflow::record_external_triggerfor programmatic embedders. Validates the run/step/state, validates the operator-supplied token in constant time, refuses replays of already-triggered steps (StepAlreadyTriggered { prior_triggered_at_ms }), and writes the payload via compare-and-swap. - Resume sentinel pass advances paused trigger steps when a payload is
recorded (mirrors the approval-decision pattern from sprint
0.3-S2c). The payload is stored asValue::String(payload); the audit hash chain captures the synthesizedoutput_hash. - Five new typed errors:
NotAnExternalTriggerStep,StepNotAtExternalTriggerGate,InvalidTriggerToken,StepAlreadyTriggered, plus an empty-payloadValidationguard. - Ephemeral runs reject external_trigger steps upfront
(review-driven).
WorkflowRunner::run(no persistence) refuses workflows that contain trigger steps with a typedValidationerror — earlier draft caught this at step-entry time, which silently allowed prior steps to execute before the typed error surfaced. - Trigger token reuse across resume (review-driven). The token is
acquired via
acquire_trigger_token: if a previously-persisted token exists for the step, it’s returned verbatim. Earlier draft generated a fresh token on every pause entry while persist-trigger-token’s “leave existing” branch kept the original; the printed value would silently disconnect from the validated value, and operators copying the just-printed token would getInvalidTriggerToken. - No fallback for entropy failure (review-driven). If
/dev/urandomcannot be read,generate_trigger_tokenreturnsErr. Earlier draft fell back to aSystemTime + pid + counterhash, which gave low-entropy observer-predictable tokens silently. - Workflow step output piping via
step_input(sprint0.3-S14). New built-in function in.ax:
Returns the JSON-encoded upstream output for the named input (declared inlet received: String = step_input("msg")workflow.json’sinputs: { msg: "upstream.result" }). Steps that need typed access parse the JSON inline. Determinism preserved: same inputs → same per-stepoutput_hashregardless of concurrency level. - New
Capability::StepInput(id=10, name=“step.input”, version=“1”). Bumpscapability_set_hash— additive surface change. Integrators using the prior hash for cache keys MUST invalidate. Old:sha256:b0ca1793.... New:sha256:980d017d.... - Compiler treats
step_input(name)as a builtin (typeck arity 1; codegen emitsOp::CapCall(StepInput, 1)). Auto-addsCapability::StepInputto the calling function’s capability set so the VM’s runtime function-cap check passes. - New
boruna_vm::capability_gateway::StepInputHandler— wraps an inner handler and interceptsstep.inputcalls. Composes with bothMockHandlerand BYOH live handlers (sprint0.3-S8). WorkflowRunner::build_step_policyauto-allowsstep.inputwhen the operator’s policy is silent on it.entry().or_insert()preserves explicit denies for hardened workflows.- Both sequential and concurrent execution paths resolve inputs coordinator-side and pass the snapshot to workers — workers hold no DataStore reference.
- Unknown input names error (review-driven, project-conventions
§1).
step_input("undeclared_name")returns a typed runtime error with the declared list for triage, instead of silently returning empty data and corrupting downstream output.
Fixed
- Sequential failure path persists actual
attempt_count(sprint0.3-S13, closes carried-forward limitation from 0.3-S11). Prior to this, the sequentialexecute_stepsfailure branch defaulted toattempt_count=1even after retry exhaustion — so a step configured withmax_attempts: 3that exhausted all 3 attempts showed up asattempt_count=1in the persisted SQL row and onworkflow show. The error message correctly said “failed after 3 attempts” but the column lied. Fix:execute_source_stepnow returnsResult<StepResult, (WorkflowRunError, u32)>carrying the count on both branches; the caller threads it through to the Failed checkpoint upsert. Concurrent path was already correct.
Added
-
workflow showsurfacesattempt_count(sprint0.3-S12). Plain mode adds anATTEMPTScolumn to the steps table;--jsonmode addsattempt_countto each step’s object. Closes the operator-visibility loop opened by 0.3-S11 — operators triaging flaky steps no longer need to query SQLite directly. -
step_checkpoints.attempt_countcolumn (sprint0.3-S11). Tracks the number of attempts each step took to reach its terminal state —1for first-try success or single-attempt failure;>1when the retry policy fired (sprint0.3-S5). Operational only — wall-clock-keyed (depends on whether transient failures happened); never feeds an audit hash. Surfaced onStepResult,StepCheckpoint, and persisted in the SQL store. First real schema migration: bumpsSCHEMA_VERSIONto2; existing v1 databases are migrated additively viaALTER TABLE ADD COLUMNwithDEFAULT 1(no rewrite, instant). The migration runner is idempotent — fresh databases (where the canonical creation script already includes the column) skip the ALTER. -
New library API:
RetryPolicy-awareretry_with_backoffnow returnsResult<(T, u32), (E, u32)>so callers can persist the actual attempt count alongside success or failure.compile_and_run_step_with_retryreturns(Value, u32)/(WorkflowRunError, u32)— same change in the runner-level wrapper.StepResult.attempt_count: u32(defaults to 1 for back-compat on older serialized JSON).StepCheckpoint.attempt_count: u32matches the SQL column.persistence::SCHEMA_V1_TO_V2_SQLandpersistence::column_existshelpers exposed within the crate.
Fixed
--skip-if-runningrace window closed (sprint0.3-S10, carried-forward debt from 0.3-S7). Prior implementation’s two-call flow (find_in_flight_runsthenrun_persistent) let two concurrent processes both pass the in-flight check and both insert new run rows. Now folded into a singleBEGIN IMMEDIATESQL transaction via the newRunCheckpointStore::insert_run_with_derived_id_skip_if_in_flightmethod: at most one of N concurrent invocations inserts; the rest cleanly Skip. Locked by an 8-thread regression test that asserts exactly 1 Inserted + 7 Skipped outcomes. New library API:WorkflowRunner::run_persistent_or_skipreturningOption<WorkflowRunResult>(Some = ran, None = skipped). The CLI flow now uses this atomic path under--skip-if-running.
Added
--expect-workflow-hash <HEX>onboruna workflow runandboruna workflow resume(sprint0.3-S9). CI/CD safety primitive that refuses to start (or resume) if the on-disk workflow def’sworkflow_hashdoesn’t match the operator-supplied expected value. Catches accidental edits, malicious mutation, and stale- checkout-vs-config drift before any side effect.--print-hashonboruna workflow validate. After validation succeeds, emitsworkflow_hash=<64-char hex>on its own stdout line — cut-friendly for shell pipelines:
Hash comparison is case-insensitive + whitespace-trim-tolerant so operators can paste from any source.HASH=$(boruna workflow validate ./wf --print-hash | grep ^workflow_hash | cut -d= -f2) boruna workflow run ./wf --expect-workflow-hash $HASH ...- Note: the hash covers the
workflow.jsonstructure only —.axstep source changes do NOT affect the hash. For full-source coverage operators should hash the workflow_dir tree at the filesystem layer.
Decided
- LLM live handler model: Bring Your Own Handler (BYOH) (sprint
0.3-S8). Boruna does NOT ship a default LLM handler in core. Integrators implement theCapabilityHandlertrait against their provider of choice (OpenAI, Anthropic, vLLM, Ollama, custom routers) and pass it toCapabilityGateway::with_handlerat workflow run time. Rationale: provider churn shouldn’t destabilize Boruna releases; API-key management belongs in the integrator’s application; production integrators (FleetQ et al.) already have their own LLM clients. New guide:docs/guides/llm-integration.mdcovers the contract, provider variants, determinism notes, and testing patterns. Reference handler atexamples/llm_handlers/openai/. Closes the open question carried since the original 0.3.0 plan;docs/roadmap.mdanddocs/limitations.mdupdated accordingly.
Added
boruna workflow run --skip-if-running(sprint0.3-S7). Idempotent invocation primitive for cron-driven scheduled workflows. Before launching a new run, queries the persistent store for any in-flight (RunningorPaused) run of the same workflow. If found, exits 0 cleanly with a stderr message identifying the prior run. Designed for the cron pattern:
Without this flag, overlapping invocations could race on the same0 2 * * * boruna workflow run /path/to/wf \ --skip-if-running --data-dir /var/lib/borunaoutputs/directory and double-bill external API calls. Persistent path only; rejected at parse with--ephemeral.- New library API:
boruna_orchestrator::workflow::find_in_flight_runs(data_dir, def),boruna_orchestrator::persistence::RunCheckpointStore::list_in_flight_runs_for_workflow.
Fixed
- Power-loss durability for
DataStore::store_output(sprint0.3-S6, closes H1/C3 deferral from 0.3-S3). Aftertempfile::persist, the parent directory is now opened and fsynced so the rename’s directory entry is journaled to stable storage. Without this, POSIX permits the dirent to be lost on power loss even though the file’s data blocks were flushed. On macOS usesfcntl(F_FULLFSYNC)for both file and directory syncs (review-driven 0.3-S6 finding) — plainfsync(2)on Darwin does NOT flush the drive’s write cache to media, which would have silently undermined the durability claim on macOS deployments. SQLite, Postgres, andgitall use F_FULLFSYNC for the same reason. Skipped on Windows (non-production target). NFS / fuse / network FS no longer claimed as covered — docstring downgraded to “use local FS for production durability claims” (review-driven finding: prior NFSv4 claim overstated; mount options + server semantics make the guarantee non-portable).
Added
- Retry policies with exponential backoff (sprint
0.3-S5).RetryPolicy { max_attempts, on_transient }on a step is now honored properly: the runner loops up tomax_attemptstotal attempts with100ms × 2^N(capped at 5s) backoff between. Both sequential and concurrent execution paths share a singleretry_with_backoffhelper, so retry semantics don’t drift between paths. Final-attempt failure surfaces as"failed after N attempts: <reason>"for operator triage. - New library API:
boruna_orchestrator::workflow::retry_with_backoffandretry_backoff_ms(pub(crate); used by tests). - Operators see retry attempts logged to stderr (gated under
cfg(not(test))so the unit suite stays silent).
Fixed
-
Retry semantics no longer cap at “retry once.” Prior code (
should_retry = ... && r.max_attempts > 1) re-attempted exactly once regardless of the configuredmax_attempts. Now honored as documented: amax_attempts: 5policy retries up to 4 times. -
retry_with_backoff’s eprintln gated undercfg(not(test))(review-driven 0.3-S5 finding #1). Prior unconditional eprintln polluted unit-test stderr and any embedder capturing process stderr. -
Integration test
tests/retry_timing.rslocks real wall-clock backoff (review-driven 0.3-S5 finding #2). Unit tests skip sleeps undercfg(test); this integration test runs in a context wherecfg(test)is NOT set on the orchestrator lib build, so the real sleeps fire and the test assertselapsed >= 250msfor a 3-attempt retry. Catches future regressions that accidentally remove the sleep. -
Concurrent step execution within a workflow run (sprint
0.3-S4). New--concurrency <N>flag onboruna workflow runandboruna workflow resume. Default1= sequential (preserves prior behavior); higher values parallelize fan-out workflows. The per-stepoutput_hashis bit-identical across concurrency levels for successful runs — the determinism contract holds. Locked by a regression test that runs the same workflow at concurrency=1 and concurrency=4 and asserts every step’s hash matches. -
Implementation: wave-based scheduler.
WorkflowValidator::topological_levelspartitions the DAG into “waves” where each level’s steps have all dependencies in earlier levels. Within a wave, source steps fan out to short-livedstd::thread::spawn’d workers (no tokio, no async runtime). Workers are pure compile+run paths returning aValue; the coordinator owns all DataStore + SQLite mutation. -
New library API:
RunOptions::concurrency: usize,ResumeOptions::concurrency: usize,WorkflowValidator::topological_levels.RunOptions::default()andResumeOptions::default()initialize concurrency to1. -
Persistent path only —
WorkflowRunner::run(ephemeral) stays single-threaded. The CLI rejects--concurrency 0at parse.
Fixed
-
Concurrent chunk halt no longer detaches sibling workers (review-driven 0.3-S4 finding #1). Prior code used
?inside the join loop, which dropped subsequent JoinHandles and detached their threads — those workers continued executing the workflow_dir even afterrun_persistentreturned. Now the join loop collects allJoinHandle::join()results into a Vec before processing, guaranteeing no thread is left running once the function returns. -
Pre-validate all chunk inputs before marking any Running (review-driven 0.3-S4 finding #2). Prior code interleaved input validation with
mark_step_running_clearing_output, so an input failure mid-chunk left earlier siblings Running on disk forever and the next resume re-executed them silently. Now a two-pass structure: pass 1 validates every chunk member’s inputs (no side effects); pass 2 marks all Running atomically and dispatches. -
Worker panics now produce attributed Failed checkpoints (review-driven 0.3-S4 finding #3). Prior panic handler only matched
&'static strpayloads (sopanic!("step {} bad", id)fell through to a generic message) and lost the step_id, leaving the panicked step at status=Running on disk. Now: triesStringpayloads first, carries the step_id alongside each JoinHandle, and records a Failed checkpoint with the panic message. -
boruna workflow show <run-id>CLI (sprint0.3-S3). Operator inspection of a single run’s full state: row, step checkpoints with truncated output preview, and approval sentinels. Plain-mode tabular output mirrorsworkflow list’s aesthetic;--jsonemits a stable pipe-friendly document forjqconsumers. ReturnsRunNotFoundfor unknown ids (project-conventions §1). -
New library API:
boruna_orchestrator::workflow::{show_run, RunDetail, ApprovalView}.RunDetailcarries ametadata_parse_error: Option<String>field so corrupt-metadata signals reach pipeline consumers (review-driven 0.3-S3 H5: stderr warnings are silently dropped when stdout is piped).
Fixed
-
Atomic-rename in
DataStore::store_output(sprint0.3-S3, closes H4 deferral from 0.3-S2c). Replaces the previousstd::fs::write(non-atomic) withtempfile::NamedTempFile::persist. Concurrent readers — including another resumed run process — see either the old contents or the new contents, never a partial torn write. Process-crash safe; full power-loss safety still requires a parent-directory fsync, documented honestly in the method docstring as the next hardening pass. -
output_hashnow equalssha256sum result.json(review-driven 0.3-S3 H2/H3). Previouslyhash_valueused compact JSON whilestore_outputwrote pretty-printed JSON, so an operator runningsha256sum runs/<id>/outputs/<step>/result.jsongot a different hex than the persistedoutput_hashcolumn — a UX footgun. All three (the hash input, the on-disk file bytes, and thestep_checkpoints.output_jsonSQL column) are now the same compact serialization. Locked by a regression test that comparessha256sum-equivalent of the on-disk bytes againsthash_value. -
workflow show --jsonno longer panics on multi-byte UTF-8 in step output (review-driven 0.3-S3 C1). Prior code did&output_json[..200]to truncate the preview field, which panicked if byte index 200 landed inside a multi-byte codepoint. Newtruncate_at_char_boundaryhelper snaps to the nearest char boundary at-or-below the byte budget. Locked by 4 regression tests covering pure ASCII, exact-boundary, multi-byte-at-boundary, and pure-multi-byte content. -
Approval-gate completion CLI (sprint
0.3-S2c). Three newboruna workflowsubcommands close the operator UX deferred from0.3-S2b:boruna workflow approve <run-id> <step-id> --data-dir <PATH>— records an approval sentinel in the run’smetadata.approvals.<step>.boruna workflow reject <run-id> <step-id> [--reason <STR>] --data-dir <PATH>— records a rejection sentinel; the optional reason surfaces as the step’serror_msgon resume.boruna workflow list [--status <STATUS>] [--json] --data-dir <PATH>— lists runs ordered by(workflow_name, run_id), optionally filtered byrunning/paused/completed/failed. Afterapprove, the operator runsboruna workflow resume <run-id>to advance the gate toCompleted(with a synthetic empty-record output whose hash is locked by a regression test) and execute downstream steps. Afterreject, resume halts the run asFailedwith the recorded reason.
-
Approval sentinel mechanism on
metadata.approvals. The runner’sPersistedRunMetadatanow carries aBTreeMap<step_id, ApprovalDecision>. Each decision recordsdecision(approved/rejected),decided_at_ms(operational only — does not feed any audit hash), and an optional human-readablereason. Backward compatible with0.3-S2bdatabases: the field defaults to empty if absent. -
New library API:
boruna_orchestrator::workflow::record_approval_decision,list_runs,ApprovalKind, plus error variantsStepNotFound,StepNotAtApprovalGate { current_status },StepAlreadyDecided { prior_decision },NotAnApprovalGateStep,RunNotResumable { terminal_status }(project-conventions §1). -
New
boruna_orchestrator::persistence::{get_run_metadata, update_run_metadata, compare_and_swap_metadata, list_runs}methods.compare_and_swap_metadatais the atomicity primitive for the approve/reject flow’s read-validate-write cycle.
Fixed
-
Race in
record_approval_decision(review-driven, 0.3-S2c). Previous implementation’s read+validate+write spanned three separate SQL transactions; two concurrent operators could both pass the in-memory prior-decision check and silently overwrite each other’s decision. Now wrapped in a CAS retry loop via the newcompare_and_swap_metadataprimitive — exactly one writer succeeds; the others surface a cleanStepAlreadyDecidederror. Locked by a 4-thread regression test asserting “exactly 1 ok, 3 already-decided.” -
Resume halt-cause attribution. When both an independently-failed step (e.g. from a crashed prior run) and a rejected approval gate exist for the same run, the resume’s
halt_with_failed_stepnow preserves the FIRST failure (the actual root cause the operator should chase) rather than overwriting with the gate rejection. -
Sentinel for non-
awaiting_approvalcheckpoint now emits an expliciteprintln!warning rather than silently no-op’ing, so operators see when their approval doesn’t apply (e.g., pre-approval for a step the workflow hasn’t reached, or stale sentinel for an already-terminal step). -
Defense-in-depth
StepKind::ApprovalGatere-validation in resume. Synthetic empty-record output is now refused for non-gate steps even if a sentinel slipped pastrecord_approval_decision’s validation (e.g. via a future code path bypass). Surfaces asWorkflowRunError::Internal. -
Persistent workflow runs survive process restarts (sprint
0.3-S2b). Wires the SQLite-backedRunCheckpointStoreshipped in0.3-S2aintoWorkflowRunner. Newboruna workflow run --data-dir <PATH>writes aruns.dband a checkpoint at every step transition. Newboruna workflow resume <run-id>picks up where a crashed or paused run left off — already-Completedsteps are restored from persisted output;Running-status checkpoints (mid-step crashes) are re-executed since the runner trusts onlyCompleted.Failedstep checkpoints in a non-terminal run halt the resume rather than silently advancing past them (review-driven regression). New--ephemeralflag opts out of persistence;--data-dirfalls back to$BORUNA_DATA_DIRthen./.boruna/data. Refuses to resume against a workflow whose hash has drifted (error_kind: workflow_hash_mismatch) and against a missingrun_id(run_not_found). Theboruna workflow approveCLI shipping in0.3-S2cwill let operators advance approval gates; until then a paused approval-gate run resumes by re-pausing. -
Deterministic
run_idderivation (project-conventions §16). Replaces the wall-clock-keyedformat!("run-{name}-{utc now}")withsha256(workflow_hash || ":" || inputs_hash || ":" || counter)[..16]hex. The counter isCOUNT(*) FROM runs WHERE workflow_hash = ?read inside an explicitBEGIN IMMEDIATEtransaction (review-driven from the initialunchecked_transactionDEFERRED-default race) so concurrent writers either see distinct counter values or hitBUSYand retry. Locked by a multi-thread regression test that fans out 8 concurrentinsert_run_with_derived_idcalls and asserts all 8 produce distinct ids. Algorithm locked by a golden-vector test computed externally. -
RunRecordandRunOperationalview structs onRunCheckpointStore. Replay-verified columns vs. operational metadata are now structurally distinct types: audit/replay code paths consumeRunRecord(nostarted_at, noupdated_at, terminal-onlyOption<RunStatus>); status dashboards consumeRunOperational. Closes the H1 review finding from0.3-S2a. The originalRunRowis retained for back-compat callers. -
New
WorkflowRunnerAPI:run_persistent(def, options, data_dir),resume(run_id, data_dir, options), andResumeOptions { policy, record, live, workflow_dir_override }.ResumeOptions::policy = Nonedefaults to the persisted policy from the original run (review-driven H2 fix; without this default the CLI’s--policyomission silently collapsed to deny-all). -
New
boruna-clifeature flagpersist-sqlite(on by default) that forwards toboruna-orchestrator/persist-sqlite. CLI surfaces a typed error rather than silently downgrading when the flag is off and a persistent run is requested (project-conventions §1).
Fixed
- Reject-at-parse footgun on persistent runs without the SQLite feature.
Previously,
cargo build --no-default-featuresproduced a CLI that silently ranboruna workflow run dir --data-dir /tmp/xephemerally, creating noruns.dband giving the operator no signal. Now the CLI errors with a clear “rebuild with default features, or pass--ephemeral” message.
Added
- Versioned capability identity (#3,
sprint
0.3-S11). Newboruna capability list [--json]CLI subcommand andboruna_capability_listMCP tool report a stablecapability_set_hashover the binary’s capability surface. Integrators use it as part of a cache key —(source_hash, policy_hash, capability_set_hash, policy.schema_version)— to safely memoize deterministic run results across binary upgrades. Algorithm, caching recipe, and per-capability versioning rules documented indocs/reference/capability-identity.md. All 10 shipped capabilities start at contract version"1". - New library API in
boruna-bytecode:Capability::ALL(canonical sorted iteration),Capability::version(),CapabilityIdentity,CapabilitySetReport,compute_capability_set_hash(),capability_set_report(). protocol_version: 1field on everyboruna-mcptool response (#6, sprint0.5-S4, pulled forward from 0.5.0 because FleetQ blocked on it for their validate-on-save UX). Wire-format version of the response envelope; bumps only on breaking shape changes (additive changes keep the version). Locked bycrates/boruna-mcp/src/tools/mod.rs::TOOL_RESPONSE_PROTOCOL_VERSIONand a 16-case regression test asserting every tool’s success and failure path carries it. Versioning policy and bump rules documented indocs/reference/mcp-server.mdunder “Stability”. Pairs withPolicy.schema_versionshipped in 0.2.0.- MCP Server Tool Reference documentation at
docs/reference/mcp-server.md— wire contract for all 10boruna-mcptools: parameter names and types, return shapes,error_kindvalues, encoding rules, and limits. Driven by FleetQ implementer feedback (post-v0.2.0 follow-up): integrators previously had to readcrates/boruna-mcp/src/server.rsto learn thatboruna_run’s parameter issource(notscript) and that there is noinputparameter. Linked fromdocs/README.md. - Structured resource limits in
boruna_run(#5, sprint0.3-S10, FleetQ P1). New optionallimitsparameter on the MCPboruna_runtool acceptingmax_wall_ms,max_output_bytes, andmax_memory_mb. Overruns return a typederror_kind: "limit_exceeded"with alimit_kinddiscriminator ("wall_ms"or"output_bytes"), the configuredlimit, and a human-readablemessage— so callers can surface clean per-limit UX instead of parsing error strings.max_memory_mbis accepted in the schema but not enforced in 0.3.x (documented as platform-best-effort pending Linuxsetrlimitwork in a future sprint). - New
boruna-vm::error::VmError::WallTimeExceeded(u64)variant andVm::set_max_wall_ms(Option<u64>)setter. Wall-clock checked every 1024 steps inside the execute loop; usesstd::time::Instant(notchrono::Utc::now()per ADR 001 determinism contract). Wall-time enforcement is wall-clock-keyed and therefore non-deterministic on overrun by construction —max_stepsremains the deterministic ceiling;max_wall_msis the operational guardrail. - Output JSON Schema validation gate in
boruna_run(#8, sprint0.5-S6, pulled forward from 0.5.0 because FleetQ wanted it in their pipeline). New optionaloutput_schemaparameter on the MCPboruna_runtool accepting any JSON Schema 2020-12 object. The script’sresultis validated post-execution; mismatches returnerror_kind: "validation_failed", phase: "output_validation"with per-path JSON Pointer errors. Malformed or oversized schemas (>256 KB) returnerror_kind: "invalid_output_schema". Schemas declaring a non-2020-12$schemaare rejected (same “reject at parse, don’t silently override” pattern as0.3-S10’sunsupported_limit). Error array capped at 100 entries withtruncatedandtotal_errorsfields. Known limitation: records/enums emit as wrapper objects; schemas for the natural shape will fail. Best for primitive returns. Seedocs/design-output-schema.md. - New
jsonschema = "0.30"dependency inboruna-mcp(default features off — noresolve-httporresolve-file, so$refto remote URLs cannot trigger SSRF or arbitrary file reads). - Record/replay for
net.fetch(#7, sprint0.5-S7, pulled forward from 0.5.0). Boruna scripts are deterministic by design; external HTTP is not. New CLI flags onboruna run:--record-net-to <FILE>(requires--live) makes real HTTP calls and persists each(method, url, request_body) → response_bodytransaction to a sidecar JSON tape file.--replay-net-from <FILE>serves responses from a loaded tape with no real network access. Strict ordered match on(method, url, request_body); mismatch returns a typed error naming the position and differing field; tape exhaustion returns a typed error; under-consumption is silently OK.- Mutually exclusive (clap
conflicts_with). If--liveis set alongside--replay-net-from, replay wins (no real calls happen).
- New module
boruna_vm::net_record_replay(feature-gated underhttp) exposingNetTransaction,NetTape,RecordingHttpHandler,ReplayingHttpHandler, andTAPE_FORMAT_VERSION. RecordingHttpHandler::with_save_path()arms save-on-drop; the CLI also probes write access on the tape path before the run starts so a CI pipeline likerecord-net-to fixtures/x.tape && verify x.tapefails fast on disk errors instead of silently producing a stale fixture (review-driven hardening).- New shared parser
boruna_vm::http_handler::parse_net_fetch_args()used by both the real handler and the recording layer so they can’t silently drift in arg interpretation. - Documentation:
docs/design-net-record-replay.md(tape format, match strategy, CLI surface, known limitations). - Per-call OpenTelemetry observability (#9,
sprint
0.4-S5, the LAST FleetQ ask). Always-ontracinginstrumentation inCapabilityGateway::callemitsboruna.capspans with attributescap.name,bytes_in,bytes_out,cap.budget_remaining,error.kind(set on the failure path:denied/budget_exceeded/runtime_error). When no subscriber is installed (the default), span macros are essentially no-ops — zero runtime cost. telemetryCargo feature onboruna-vm(and mirror feature onboruna-cli) adds an OpenTelemetry OTLP-over-HTTP exporter (opentelemetry 0.27+opentelemetry-otlp 0.27+tracing-opentelemetry 0.28). New helperboruna_vm::init_telemetry()readsOTEL_EXPORTER_OTLP_ENDPOINT(and optionalOTEL_SERVICE_NAME, defaulting to"boruna"); returns aDisabledno-op handle when the endpoint is unset (Boruna behaves identically to a non-telemetry build), installs the exporter when set. Returns aTelemetryHandlewhoseDropflushes pending spans.- CLI integration:
boruna-clibuilt with--features telemetrystarts a tokio runtime inmain, callsinit_telemetry()BEFORE parsing CLI args, holds the handle for the binary lifetime, and on shutdown drops the handle THEN drains the runtime with a 5-second timeout (so in-flight OTel HTTP POSTs complete instead of being killed byprocess::exit). - New documentation:
docs/design-otel.md(span shape, attribute table, determinism contract, library-version pin set, BYO-subscriber fallback path). boruna_orchestrator::persistence::RunCheckpointStore— SQLite-backed workflow checkpoint store (sprint0.3-S2a). Implements ADR 001 step 1–5: schema, Connection setup with mandatory PRAGMAs (journal_mode=WAL,synchronous=NORMAL,foreign_keys=ON,busy_timeout=5000), CRUD operations (insert_run,update_run_status,get_run,list_runs_by_status,upsert_step_checkpoint,list_step_checkpoints), and aBEGIN IMMEDIATEretry policy that handles bothSQLITE_BUSYandSQLITE_LOCKEDwith exponential backoff (10ms→50ms→250ms→1.25s) before failing withPersistenceError::Busy. Not yet wired intoWorkflowRunner— that integration lands in0.3-S2b(along withboruna workflow resume <run-id>and--data-dir).- New
persist-sqliteCargo feature onboruna-orchestrator(default-on). Addsrusqlite = "0.32"with thebundledfeature so SQLite compiles from C source — preserves the musl-static-binary story per ADR 001. - Schema embedded via
include_str!("schema_v1.sql"). Single-rowschema_versiontable withCHECK (id = 1)constraint structurally prevents stale-row accumulation across migration attempts. PersistenceError::NotFound { entity, key }returned byupdate_run_statuswhen the targetrun_iddoes not exist (review- driven; silent-no-op was rejected as a footgun for the resume path).upsert_step_checkpointusesCOALESCE(excluded.X, existing.X)forstarted_at,output_json,output_hashso a partial upsert (e.g. step transition from Running to Completed without re-supplying started_at) preserves the original value rather than clobbering to NULL (review-driven; locked by two regression tests).docs/design-persistence-store.md— sprint scope split rationale, acceptance criteria, schema annotation conventions.
Decided
- ADR 001 — Persistence Backend (
docs/adr/001-persistence-backend.md). SQLite viarusqlite/bundledchosen as the workflow-checkpoint backend. No persistence-trait abstraction in v1 — direct concrete dependency. Includes a determinism contract for persisted state (operational vs. replay-verified columns), the writer serialization model, mandatory connection PRAGMAs (journal_mode=WAL,foreign_keys=ON,busy_timeout=5000), and an illustrative schema. Unblocks0.3-S2through0.3-S9— the entire 0.3.0 critical path. Sprint0.3-S1.
0.2.0 - 2026-04-25
Driven by implementer feedback from FleetQ (production integrator). This release closes the two P0 adoption blockers; remaining P1/P2 asks are tracked as issues #3–#9.
Added
- MCP
boruna_runtool now accepts a structuredPolicyobject for thepolicyparameter, in addition to the existing"allow-all"/"deny-all"string shorthands. This exposes the per-capability rules (allow,budget),default_allowmode (allowlist vs. denylist), andnet_policy(allowed domains, methods, byte limits, timeout) that the VM has always supported. Seedocs/reference/policy-schema.mdanddocs/reference/policy.schema.json. - New documentation:
docs/reference/policy-schema.md(prose + examples) anddocs/reference/policy.schema.json(machine-readable JSON Schema 2020-12) for integrators rendering capability matrices in their own UIs. - The
boruna_runMCP tool description now advertises the structured-policy capability so AI agents discover it from the tool list directly. - Multi-target release workflow (
.github/workflows/release.yml) that publishes static binaries on everyv*tag forx86_64-unknown-linux-musl,aarch64-unknown-linux-musl,x86_64-apple-darwin, andaarch64-apple-darwin, plus a combinedSHA256SUMSchecksum file. Linux builds use musl so the binaries run on Alpine and other libc-minimal distributions. docs/releasing.md— release process, verification, and rationale for using GitHub-hosted runners (vs. the self-hosted runner used byci.yml).- README install section showing curl-and-verify install.
Changed
- Breaking (MCP only):
boruna_runnow rejects unknownpolicyvalues (e.g. typo’d strings, numbers, arrays) withsuccess: false, error_kind: "invalid_policy"instead of silently treating them as"allow-all". The legacy strings"allow-all"and"deny-all"continue to behave identically.
0.1.0 - 2026-02-21
Added
- Deterministic workflow execution engine with DAG validation and topological ordering
- Hash-chained audit logs (SHA-256) and self-contained evidence bundles for compliance
- Policy-gated capability system — 10 capabilities:
net.fetch,db.query,fs.read,fs.write,time.now,random,ui.render,llm.call,actor.spawn,actor.send - Replay engine for determinism verification via
EventLogcomparison - Three reference workflow examples:
llm_code_review— linear 3-step pipeline demonstrating LLM capability and evidence recordingdocument_processing— fan-out/merge 5-step pipeline demonstrating parallel steps and DAG schedulingcustomer_support_triage— approval-gate 4-step pipeline demonstrating human-in-the-loop and conditional pause
- MCP server (
boruna-mcp) exposing 10 tools over JSON-RPC stdio for AI coding agent integration - Actor system with
OneForOnesupervision and bounded execution scheduling (Vm::execute_bounded) boruna-tooling: diagnostics with source spans, auto-repair, trace-to-tests, stdlib test runner, 5 app templatesboruna-pkg: deterministic package system with SHA-256 content hashing, dependency resolution, and lockfiles- Real HTTP handler (feature-gated via
boruna-vm/http) with SSRF protection fornet.fetchcapability - CLI binary (
boruna) with subcommands:compile,run,trace,replay,inspect,ast,workflow,evidence,framework,lang,trace2tests,template - Standard library: 11 deterministic libraries —
std-ui,std-forms,std-authz,std-http,std-db,std-sync,std-validation,std-routing,std-storage,std-notifications,std-testing - 557+ tests across 9 crates