All notable changes to Boruna are documented here. Format follows Keep a Changelog. Versioning follows Semantic Versioning.
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.
--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.
--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.
- 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.
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.
- 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).
- 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).
-
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.
-
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).
-
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.
-
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).
- 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.
- 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.
- 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.
- 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.
- 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
- 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