Changelog
Unreleased
Section titled “Unreleased”[0.28.0] — 2026-08-01 — Field Reports
Section titled “[0.28.0] — 2026-08-01 — Field Reports”Five field reports and what they turned up. No breaking changes; no migration. The through-line is failures that reported the wrong thing rather than nothing — a config field parsed and validated and then dropped, a decision cap that was correct but unexplained, a deployment that hung with no decision emitted, a helper path resolved against the wrong directory.
sigil runresolvesrequire('lib.x')from the scenario tree you named, not from its own working directory. It previously resolvedlib/the way the configured modes do — against a[[scenarios]]root — despite having no config to read, and so fell through to a relativescenarios/run/lib/. A scenario tree staged anywhere else could not load its own helpers at all; and because the path was relative it resolved against sigil’s working directory, so a run launched from inside another repository would read that repository’s helpers if it had any. Resolution now uses the discovery anchor — the directory argument a scenario was found under, or a file argument’s parent — as an absolute path. See Whererequire('lib.X')resolves.sigil run --lib-dir <DIR>names that directory outright, for tools that embed sigil as a runner over a tree they stage themselves. Validated before any scenario runs, so a bad value aborts the invocation instead of surfacing as a require failure partway through a suite.[deploy].startup_timeoutis honored instead of silently ignored. No code read it at all: it was parsed, defaulted, validated and documented, then dropped, for every backend. It now bounds the compose start command — image pull, container create/start — whilehealth_timeoutcontinues to bound the readiness poll, so each name means the phase it describes.- A wedged
docker compose upcan no longer hang an eval forever. It ran with no deadline, so a stuck registry pull blocked indefinitely with no decision emitted. It is now killed and reaped on expiry and fails closed. Teardown stays unbounded deliberately — a cut-shortdownwould leak containers. sigil eval --failure-artifact <FILE>writes a fixed-vocabulary failure phase as JSON on every terminal outcome, including runs that produce no eval report. A caller that must preserve holdout secrecy can now tell a failed deploy leg from an undecryptable scenario set from an uncovered strict MUST from an internal evaluator error, without parsing stderr. The artifact carries only that closed vocabulary and config field names — never scenario ids, endpoints, rubric text, config values, or raw error strings. Each structured deployment failure also reports which config fields came from the working checkout, the control snapshot, a CLI flag, or the environment.sigil decideexplains the mode cap. Onlyautocan ever ALLOW, so a clean evaluation inshadoworadvisoryreturns REVIEW — unchanged, but previously unexplained. The rationale now carriesshadow_mode_cap/advisory_mode_cap/policy_mode_unset_cap, and--jsonaddspolicy_mode,mode_ceiling,mode_cappedand the fulllattice. A REVIEW that came from real findings carries no cap code, so an operator can confirm a clean shadow run without opening a protected report.sigil keys addworks on a fresh project and is idempotent. It creates the[keys]table when sigil.toml lacks one, and re-adding a name with the same recipient is a successful no-op that never prompts — safe to run repeatedly from headless provisioning. Re-adding with a different recipient still confirms interactively, but now fails closed without mutating sigil.toml when there is no terminal to confirm on;--forcereplaces it non-interactively.
[0.27.0] — 2026-07-29 — Scenario Store Layout
Section titled “[0.27.0] — 2026-07-29 — Scenario Store Layout”Breaking: existing projects must run sigil migrate — dry-run by default, add --apply to write. Scenario ids are unchanged by the move, so ledger history stays comparable.
- Scenario source moves out of
.sigil/to the project root..sigil/scenarios/<svc>/{visible,holdout,staging}/becomesscenarios/<svc>/..sigil/now holds only tool state — config, blobs, ledger, proofs, generated type stubs — while scenarios live where they can be found and edited like any other source. See Writing Scenarios. - The file extension is the source of truth for visibility.
foo.luais visible,foo.lua.ageis an age-encrypted holdout,foo.staged.luais generated output awaiting promotion.lib/is the only reserved directory name. Three invalid states stop being representable: a plaintext.luaunderholdout/was a leaked holdout, and there is noholdout/now; a directory and an extension could disagree about visibility, and there is one signal; and promoting a scenario was a cross-tree move that brokegit log --follow, where it is now an encrypt in place at the same path. sigil migrate— one command to upgrade. Refuses a dirty git worktree, refuses when both layouts are populated, and refuses to move an unencrypted file out ofholdout/(in the new layout that name means visible, so migrating it would publish it). Usesgit mvso history follows each file, is idempotent, and verifies the scenario-id set is identical before and after — a migration that would silently drop or rename a scenario aborts rather than reporting success.- Promoting to holdout without configured recipients is now an error. It previously warned and wrote plaintext, which was survivable when a directory carried the designation. Now that visibility is the extension, an unencrypted holdout is indistinguishable from a visible one, so that path silently published held-out scenarios to the coding agent. Promotion is all-or-nothing.
[[scenarios]]config, with multi-root support for monorepos. Declare where scenario trees live, and colocate them with the service they exercise rather than forcing one central tree. Each root resolves its ownlib/. Replaces the old[scenarios]table. See Configuration.- Sealed-recipient manifests are embedded in the age header, replacing the
.sealed.jsonsidecar. A manifest cannot drift from the file it describes, is readable with no key at all — the point being that you need to know who a bundle was sealed to precisely when you cannot open it — keeps the file decryptable by stockage/rage, and is covered by age’s header MAC, so a forged recipient list makes the file fail to decrypt. It remains an assertion, not proof of identity: a missing manifest means unknown recipients, never verified. - Holdout scenario set hashes change once, at migration. Re-sealing yields new ciphertext, and an embedded manifest changes which digest is hashed.
sigil migraterecords ascenario.layout_migratedledger event soreplay/diffcan explain the discontinuity instead of presenting prior evals as corrupt.
[0.26.0] — 2026-06-25 — Decision-Gate Hardening + Structured Judge
Section titled “[0.26.0] — 2026-06-25 — Decision-Gate Hardening + Structured Judge”- Security gates fail closed. Mark a scanner
requiredand a scanner that can’t run — missing binary, timeout, or unparseable output — now blocks the merge instead of silently passing. New per-scanner modes (disabled/advisory/required) and a configurable per-scanner timeout (default 120s). - The security report now reaches the decision. Fixed: scan findings were not being carried into
sigil decide, so a failing security gate didn’t actually influence ALLOW / REVIEW / BLOCK. Findings are now round-tripped from the eval through to the decision end-to-end. - An eval without durable evidence can’t ALLOW. If sigil can’t persist the report, scenario evidence, eval DAG, or ledger event, the eval is marked degraded and the decision drops to REVIEW — a score you can’t reproduce shouldn’t earn autonomy.
- More reliable LLM judging. The judge now uses structured tool/function calling instead of parsing prose, so verdicts come back as typed values — no more “the model wrote an essay instead of JSON,” and no fragile score-scraping. Works across OpenAI-compatible, tool-calling, and command providers, with a JSON-mode fallback; malformed output is a judge failure, never a silent pass.
- Faster ad-hoc contracts. A one-line scenario —
return { run = function() ... end }— now runs as-is: capabilities are inferred from the body andpriorityis optional on thesigil runpath. Committed suites still get the full strictsigil scenario lint. - New guide: the contract handoff. An end-to-end walkthrough of authoring a contract in sigil, packaging it as a
.wic, and verifying it as a provider with wraith — including the exit-code andfailure_classmapping. See Guides → .wic handoff.
[0.25.0] — 2026-06-13 — Intent-Contracts Surface + Untrusted-Input Hardening
Section titled “[0.25.0] — 2026-06-13 — Intent-Contracts Surface + Untrusted-Input Hardening”sigil.check(expr, label)— advisory checks: a nonfatal tier alongsideexpect(). Outcomes are recorded in the report but never fail the scenario or change the exit code. The expression is evaluated defensively, so a check against a field that drifted away (e.g.auth.json.id) records an evaluation error instead of crashing the run. Each appears in a per-scenariochecks[]block.sigil run --env KEY[=VALUE]— docker-style, repeatable.--env FOO=barsets a value; bare--env FOOpasses a value through from sigil’s own environment so secrets never appear on the command line. Strict allowlist: only named keys reachsigil.env().- Endpoint pinning (security). By default
sigil runconfines every HTTP call and redirect to the--endpointorigin — a scenario can no longer redirect a request to an arbitrary host (cloud metadata endpoints, attacker origins) viabase_url, even one built at runtime.--allow-cross-originopts out. In configured modes, a[eval] allowed_originsallowlist permits known sidecars. - Machine-readable failure detail in
--json. Per-scenario entries now carryfailure_class("assertion"for a behavior problem,"crash"for a tooling/runtime problem,"pinning"for an endpoint-pinning violation) and anexpects[]array recording everyexpect()outcome with its source and lifted---description — so tools can branch on outcomes without scraping error text. sigil scenario lint-path <paths...> --json— lint arbitrary.luafiles with machine-readable findings, no project layout required.sigil scenario run --format json— the configured runner now emits the same report schema assigil run --json.- New scenario lints — unknown capability names (E005) and the
wraithhelper capability declaration (E006) are now caught statically. - Faster, hardened scenario parsing. An owned Lua 5.4 lexer replaces the previous tokenizer on the scenario-rewriting path: ~2.5× faster, and hardened so malformed or hostile scenario files fail with a normal report entry instead of ever crashing the
sigilprocess. Deep nesting, runaway memory allocation, and pathological input are all bounded; a scenario’s declared time budget is now honored (capped at the operator’s ceiling).
[0.24.0] — 2026-06-07 — Native Browser Hardening
Section titled “[0.24.0] — 2026-06-07 — Native Browser Hardening”sigil run --json— machine-readable run report on stdout ({status, total, passed, failed, scenarios:[…]}); exit codes unchanged, human output still the default.sigil runis quiet by default — internal browser-session logs are gated behindRUST_LOG=info; in--jsonmode all logs stay on stderr so stdout is valid JSON.- Browser reliability —
open/reload/back/forwardwait for the page load event before returning; the remainingsigil.browser.*getters (value,attr,count,visible,enabled, …) are wired and now raise on backend errors instead of silently reportingfalse;screenshot("path")writes a sandboxed PNG file. - Migrated to the published
asupersync0.3.2 crate (capability-secure IO model), fixing CI.
[0.23.0] — 2026-05-27
Section titled “[0.23.0] — 2026-05-27”sigil run [PATHS...]— minimal scenario runner. No.sigil/sigil.tomlrequired. Walks files/directories for*.lua(recursive,lib/skipped), runs each via the in-process scenario runtime, prints pass/fail + summary. Flags:--filter <SUBSTR>(repeatable, OR’d, substring on path and scenario title)--tag <T>/--exclude-tag <T>(existing scenario tag semantics; exclude always wins)--endpoint <URL>(optional; surfaces a clear error at first HTTP call if unset, so browser-only / client-side scenarios can run without one)- Exit codes: 0 all passed, 1 some failed, 2 zero scenarios matched (pytest convention)
[browser] headlessconfig +SIGIL_BROWSER_HEADLESSenv override — set tofalseto see the browser window during local development.- Clearer
sigil install-browseroutput — distinguishesReusing cached chrome …on cache hit fromUsing system chrome …(with--use-system) andInstalled chrome …(fresh download).
- Native browser backend (the 0.22.x cutover default) now functional end-to-end. The CDP bridge was previously stubbed: every
sigil.browser.*call returnedErr(Shutdown)in <1ms without ever launching Chrome. This release wiresJobKind::CdpCall/AwaitEvent/AwaitDownloadthrough the live CDP client, event router, and download tracker; primes a default page session on launch and routesPage.*/Runtime.*/etc. accordingly. NativeBrowserManager::plan_callhandles 14 of 17BrowserCallvariants (was 7). Wired:Fill,Wait,Html,Type,Press,Hover,Check,Select,Scroll,Checked,WaitDownload,Cookies,Pdf,Snapshot,Upload.sigil install-browserHTTPS transport wired through asupersync — pinned Chrome-for-Testing zip downloads + verifies + extracts in a single command on a clean host. Distinct error variants for DNS / TLS / connect / partial-body / HTTP-status failures.sigil install-browsercache-hit writes thecurrentpointer sosigil browser doctordoesn’t immediately report the binary as missing after a successful pre-populated install.sigil browser doctoruse_system_fallbackrow reads coherently across all four states (Sigil install + no system, Sigil install + system available, system only, neither).scenario run --deployretry race — back-to-back invocations no longer fail with “Error deploying service” after killing a stale process. Port-readiness polling after SIGTERM (with SIGKILL escalation after 2s), foreign-pid guard via/proc/net/tcp{,6}.cdp/clientintegration tests un-#[ignore]d (7 tests). Root cause was a dangling reader task keeping the server-side TCP half open.- Workspace clippy gate (
cargo clippy --workspace --all-targets --locked -- -D warnings) now exits 0 (was ~213 errors in test code). Added to CI.
Security
Section titled “Security”sigil.browser.uploadroutes every file path through a per-scenario path sandbox beforeDOM.setFileInputFiles. Previously caller-supplied paths went straight to Chrome, letting an untrusted scenario attach any file on the host (/etc/passwd, SSH keys, etc.) to a form. The sandbox canonicalizes paths after symlink resolution, rejects escapes (OutsideAllowedRoots), and fail-closes on a missing root. Allowed root is the scenario file’s parent directory, so uploads can only reach fixtures sitting next to the scenario.
Performance
Section titled “Performance”- Native browser only launches when a scenario declares
"browser"capability. Previously every scenario eagerly spawned Chrome (twice — once per PR/baseline env) before policy was even parsed. Pure-HTTP scenarios insigil eval --tag healthwent from ~282ms/scenario to ~1ms/scenario. - Lazy browser-init backstop: even browser-declared scenarios that never actually call the browser don’t pay launch cost.
- Mis-declared scenarios that call
sigil.browser.*without the"browser"capability now fail with a clear policy error instead of silently launching Chrome anyway.
BREAKING (carried from prior cutover)
Section titled “BREAKING (carried from prior cutover)”[browser] backenddefault flipped fromclitonative. Browser scenarios now run in-process via thesigil-browsercrate by default. The CLI backend (agent-browser) shell-out path was removed;backend = "cli"still deserialises (so existing configs do not fail to parse) but everysigil.browser.*call returns a structured “removed” error directing operators to setbackend = "native".
[0.21.0] — 2026-04-30
Section titled “[0.21.0] — 2026-04-30”-
Scenario DSL:
sigil.sleep()primitive for timing control with budget enforcementsigil.expect_status_class()helper for HTTP response class assertions (2xx, 4xx, 5xx)- Per-scenario reset hook via
[scenario.reset]config - Per-call
base_urloverride on HTTP methods
-
Scenario management:
scenario promotecommand for staging → holdout workflow- Holdout split support during promotion
--seedflag for deterministic scenario generation- Tag-based filtering:
--tagand--exclude-tagselectors for all scenario commands - Scenario-level skip directives with reasons in reports
-
Scenario generation:
--filterand--limitflags for scenario generate plan scope- Per-case logging in scenario generation
-
Judge system:
--judge-modelflag to compare judge outputs across different modelssigil comparecommand for side-by-side judge evaluation
-
Configuration:
- Prompt injection of configured
SIGIL_SEED_KEYSinto generation - Enhanced few-shot examples for spec-to-logs mapping
- Improved JSON error handling with mode hints
- Prompt injection of configured
- Scenario
runanddry-runnow honor skip directives - Promotion correctly handles staging paths
- Staging-category names no longer leak into scenario tags
- Judge output now deterministic across runs (fixed parameter settings)
[0.20.1-rc.1] — 2026-04-21
Section titled “[0.20.1-rc.1] — 2026-04-21”-
Scenario generation CLI:
sigil scenario generateorchestrator for end-to-end generation- Stage 3 execution validation (opt-in via
--verifyflag) - Scenario-level skip with reason surfacing in eval reports
--tag/--exclude-tagselectors forscenario runandscenario dry-runscenario promotesubcommand for staging → visible/holdout split
-
Scenario DSL enhancements:
--seedflag for deterministic generation--filterand--limitfor generation scope control
-
Judge system:
--judge-modelflag for cross-model comparisonsigil comparecommand- Claude-code provider for judge with structured output
-
CI integration:
sigil cicommand for PR evaluation and GitHub status- Config context resolution via frontmatter
-
Browser automation:
sigil.browserAPI:open,click,fill,wait,text,html,title,url,screenshot,eval,cookies,snapshot,visible- Session isolation per scenario
-
Agentic intent:
sigil.intent()for LLM-driven scenario execution- Tool-use with automatic tool descriptors
- Capture fields for structured data extraction
- Thinking model support
-
CLI enhancements:
sigil keys add-selffor key managementsigil scenario run --allfor batch executionsigil feedback --lastfor agent dev loop--no-baselineflag forsigil eval--deployflag forsigil scenario run(self-contained execution)- Progress reporting for eval and scenario run
- Format auto-detection and shell completion
- Judge parameter settings for deterministic output
- Judge provider argument handling
- Scenario CLI log buffer handling
- Scenario skip directive processing
[0.20.0] — 2026-03-05
Section titled “[0.20.0] — 2026-03-05”-
Kubernetes backend:
sigil evalnow supports Kubernetes deployments via kubectl- Configure via
[deploy]section in sigil.toml
-
Container backends:
- Bare container backend for
docker run/podman runsingle-container services - Configurable compose command (docker-compose, podman-compose, etc.)
- Bare container backend for
-
Endpoint management:
--pr-endpointflag to evaluate against specific PR endpoint--baseline-endpointflag to evaluate against specific baseline endpoint
-
Documentation:
- Comprehensive Scenario DSL user reference
- CLI help text improvements with examples
[0.19.0] — 2026-03-05
Section titled “[0.19.0] — 2026-03-05”- Deploy backend selection: Configure primary backend in
sigil.toml - Compose CLI flexibility: Support for podman-compose, docker-compose variants
- Endpoint control:
--pr-endpointand--baseline-endpointflags for custom deployments
[0.18.0] — 2026-03-05
Section titled “[0.18.0] — 2026-03-05”- GitHub Actions integration: Deploy and verify via GitHub workflows
- CLI improvements: Long help text, workflow examples, agent-friendly documentation
[0.17.0] — 2026-03-05
Section titled “[0.17.0] — 2026-03-05”-
Attestations: In-toto attestation generation and Ed25519 signing
-
Output formats:
- JSON format shorthand:
--jsonalias for--format json - Format auto-detection: pretty for TTY, text for pipes
- Shell completion generation
- JSON format shorthand:
-
Diagnostics:
- Enhanced
sigil doctorwith comprehensive prerequisite checks
- Enhanced
[0.16.0] — 2026-03-05
Section titled “[0.16.0] — 2026-03-05”-
Dashboard: Web UI for eval and trust overview
-
Trust commands:
sigil trust show: View current trust statesigil trust history: Review trust transitionssigil trust mode: Check and transition trust levels
-
Eval enhancements:
- Failure-triggered baseline re-check
sigil report: Reconstruct eval reports from ledger
-
Policy hooks: Optional OPA/Rego policy verification
[0.15.0] — 2026-03-05
Section titled “[0.15.0] — 2026-03-05”- Adaptive evaluation: Early termination based on confidence
- LLM judge:
sigil.judge()Lua API for semantic assertions - Judge configuration:
[judge]section in sigil.toml with provider selection - Evaluation:
sigil difffor comparing two evaluation results - Judge providers: Support for multiple judge backends
[0.14.0] — 2026-03-05
Section titled “[0.14.0] — 2026-03-05”- Replay:
sigil replayto re-execute scenarios from recorded artifacts - Reporting:
sigil reportto reconstruct reports from ledger
[0.13.0] — 2026-03-05
Section titled “[0.13.0] — 2026-03-05”- Security gates:
- Automated secret scanning (trufflehog)
- Dependency vulnerability scanning (trivy)
- Static analysis (semgrep) for code quality checks
[0.12.0] — 2026-03-05
Section titled “[0.12.0] — 2026-03-05”- Parallel execution: Concurrent scenario runs for faster evaluation
- Judge consensus: Quorum voting across multiple judge instances
[0.11.0] — 2026-03-05
Section titled “[0.11.0] — 2026-03-05”- Trust model: Per-service trust scoring
- Judge fallback: Automatic fallback to secondary model on provider failure
[0.10.0] — 2026-03-05
Section titled “[0.10.0] — 2026-03-05”- GitHub Actions:
sigil-actionworkflow integration
[0.9.0] — 2026-03-05
Section titled “[0.9.0] — 2026-03-05”- Policy engine:
sigil decidewith threshold-based approval
[0.8.0] — 2026-03-05
Section titled “[0.8.0] — 2026-03-05”- Evaluation reports: JSON eval reports with detailed results
[0.7.0] — 2026-03-05
Section titled “[0.7.0] — 2026-03-05”- Baseline comparison:
sigil evalcompares PR against baseline - Satisfaction scoring: Quantified results vs baseline
[0.6.0] — 2026-03-05
Section titled “[0.6.0] — 2026-03-05”- Scenario execution:
sigil evalruns scenarios against deployed environments - Type stubs:
sigil generate-typesfor LuaLS IDE support - Blob store: Content-addressed artifact storage with integrity verification
[0.5.0] — 2026-03-05
Section titled “[0.5.0] — 2026-03-05”- Scenario runner:
sigil scenario run <scenario>for local development - HTTP client:
sigil.get(),sigil.post(),sigil.put(),sigil.patch(),sigil.delete() - CLI runner:
sigil.exec()for command execution
[0.4.0] — 2026-03-05
Section titled “[0.4.0] — 2026-03-05”- Project setup:
sigil initscaffolds new sigil projects - Health checks:
sigil doctorvalidates environment and dependencies - Lua API:
sigil.*globals:env(),json(),yaml()
[0.3.0] — 2026-03-05
Section titled “[0.3.0] — 2026-03-05”-
Scenario DSL:
expect(expr)with power assertionsinvariant(name, opts)for property testing- Generators:
sigil.gen.string(),sigil.gen.int(), etc.
-
Key management:
sigil keyscommands for scenario encryption -
Holdout scenarios: Support for hidden test scenarios
-
Scenario management:
sigil scenario list,sigil scenario dry-run
[0.2.0] — 2026-03-05
Section titled “[0.2.0] — 2026-03-05”- Initial public release
- Core evaluation engine
- Scenario support with Lua DSL
- Docker Compose deployment
- Basic evaluation reporting