Changelog
Unreleased
Section titled “Unreleased”[0.36.0] — 2026-09-28 — Proven Gates
Section titled “[0.36.0] — 2026-09-28 — Proven Gates”sigil decide now has to earn its ALLOW. A service climbs trust one recorded
step at a time, any regression needs a human, and every path a September audit
found failing open now fails closed. A formal-verification pass then put the
decision gate under Kani proofs and the ledger, blob store, browser runtime and
plugin host under property tests, fuzzing, loom, Stateright and crash
simulation, and fixed what those tools found. Config keys that nothing read are
gone.
Breaking
Section titled “Breaking”sigil decidegates on earned trust. Decide records each trust step as atrust.transitionortrust.decayledger event and gates on the lower of that earned level and the level the current score implies. A service with no recorded transitions starts at NONE, even with a high score. It earns LOW after 20 clean evals while its mode is explicitlyshadow(an unset mode does not earn), MEDIUM after 50, and HIGH after 100 with no incidents.autoneeds MEDIUM to ALLOW an R0/R1 change. A Sev-1/Sev-2 incident or an override rate above 10% drops the level to LOW, and inactivity decays it. Action: setmode = "shadow"(orsigil policy set --service <svc> --mode shadow) on services that have not earned trust, and expect REVIEW withtrust_level_insufficient:<none|low>fromautoservices until they climb.- Any regression now REVIEWs. A P1 or P2 regression caps the decision at REVIEW (
regression_detected); before, enough improvements could outweigh it. P0 regressions still BLOCK. - Config keys that nothing read are removed. Sigil now reports them as unknown keys and ignores them; removing them changes no behaviour. Action: delete these from
.sigil/sigil.toml:[holdout](the whole section)[ledger]backend,remote,auto_sync,sync_retry[feedback]include_status_codes,include_step_names,include_timing[policy.<service>]max_regression_rate,window,max_human_override,trust_decay_days[security]sbom[observability]log_format,log_level,trace_enabled,metrics_endpoint(use the newmetrics_textfile)[attestation]format
- Pairwise judging, differential evaluation, baseline re-check and adaptive stopping are removed. None had a production caller; every scenario already ran once per lane. Action: delete
[judge] pairwise,[eval] differential,[eval.differential_eval]and[eval] runs_per_scenario. The judge config hash changes, so it will not match evals recorded by earlier versions. - The GitHub Action’s
trustoutput is nowpolicy_mode. The old output was alwaysNONE. Action: readsteps.<id>.outputs.policy_modeinstead ofoutputs.trust.
Gates that now fail closed
Section titled “Gates that now fail closed”sigil cinever ALLOWs whensigil decidefails. A decide error gives REVIEW (or BLOCK for failing evals below 0.5 satisfaction), auto-merge never runs, the status readssigil: decide failed, needs review, and the JSON report carriesdecide_error.- A PR cannot change the policy it is graded by. Changes under
.sigil/(except.sigil/types/),scenarios/or any[[scenarios]]root,.github/,action.yml, orCODEOWNERSREVIEW withgovernance_files_changedin every mode. - Only a complete, bound eval can ALLOW. Tag-filtered or budget-cut evals REVIEW (
partial_scenario_set); so do evals whose holdouts did not load (scenarios_not_loaded) and evals against--pr-endpoint/--baseline-endpointunless the control config setsbackend = "external"(endpoint_override). Decide uses only the eval for the PR’s own artifact. Progressive mode keeps its 600-second default budget, so a suite that exceeds it now REVIEWs until you raise[eval] budget_seconds. - Risk classification sees the PR’s own changes. The diff is now
<control-ref>...<pr-ref>; before, CI PRs classified R0. Root-levelmigrations/*.sqlis R3.sigil risk classifygains--base. - OPA and break-glass hold. Any OPA error REVIEWs (
opa_error) even withrequired = false, and--break-glassno longer lowers an OPA denial. - Configured security scanners are required by default. A named scanner that is missing or crashes fails the gate; set
sast_mode,secret_modeordep_modeto"advisory"to opt out.block_onnow defaults to["critical", "high"]andwarn_onto["medium"]; an unknown severity such as"HIGH:auth"is a config error. - Invalid scores and clocks cannot ALLOW. Scores outside
[0, 1], non-finite values, and evals stamped more than 5 minutes in the future REVIEW. An unrecognised incident severity weighs as Sev-1. - The ledger an ALLOW rests on is re-checked after sync. Decide re-runs the chain check and trust on the events its pre-ALLOW sync pulls, and REVIEWs with
eval_superseded_by_syncwhen a newer eval of the artifact arrived. The freshness bound now measures the sync itself.
New tools
Section titled “New tools”sigil ledger verifychecks that every line’s content hash matches, every parent hash resolves, and each agent’sseqvalues have no gap;decideREVIEWs withledger_chain_broken. It detects corruption and removed or edited events that later events depend on. It is not tamper evidence: the hash is unkeyed, so truncating the newest events or rehashing a rewritten chain goes undetected.sigil trust historynow shows real transitions and decays.- Signed decision attestations. With
[attestation] signing_keyset, decide signs an in-toto attestation and reports it asdecision.attestation;sigil verifysucceeds for a real eval. - Prometheus metrics.
[observability] metrics_textfilewrites per-run metrics for the node_exporter textfile collector. - Behaviour-preserving PRs can ALLOW. When no scenario changes outcome and enough still pass (8 with
min_confidence = 0.8), a refactor can ALLOW inauto(NO_CHANGEevidence). - REVIEWs name their cause in
automode:risk_class_r3_requires_review,trust_level_insufficient:<none|low>, orrisk_class_r2_requires_high_certainty.
Holdouts, judges and evals
Section titled “Holdouts, judges and evals”sigil evaloutput hides holdout detail by default. The summary,--format jsonand--outputshowholdout_<n>;--holdout-detailrestores ids, titles and errors on a trusted terminal. Holdout fragments are also scrubbed from logs.- Prompt-injection detection scans the observation, not the judge’s reasoning. A hit makes the step unevaluable (REVIEW), and
[judge] injection_detectionnow works. sigil.intenthonours data classification, the eval token budget is enforced and recorded, judge calibration computes Cohen’s kappa correctly, andsigil replay --liverefuses a different scenario set.- The process backend evaluates two different trees. PR and baseline each run from their own worktree; the build must run inside
[deploy.process] command. Every deploy recipe hash changes.
Reliability
Section titled “Reliability”- Concurrent and interrupted ledger writes are safe. Appends take a ledger-wide lock, syncs hold it, a sync killed mid-rebase is recovered on next use, and a torn final line no longer corrupts the next event. The SQLite projection rebuilds once on first use and now sees events that syncs pull late.
- The blob store survives power loss and concurrent gc. Puts fsync their directories, dedup hits refresh the mtime so gc keeps them, and
sigil store gcremoves stale temp files. - Smaller fixes cover browser navigation timeouts, fast downloads, plugin cancellation and teardown, Ctrl+C cleanup, log scrubbing of overlapping secrets, and
sigil generate-typesstubs forsigil.browser.
Upgrade Temporal 0.1.0 to 0.1.1 first: 0.1.0’s manifest excludes Sigil 0.36.0. See Trust Model, Ledger, Configuration, and CLI Reference.
[Temporal 0.1.1] — 2026-09-12 — Bounded Workflow Helpers
Section titled “[Temporal 0.1.1] — 2026-09-12 — Bounded Workflow Helpers”The Temporal plugin gains an opt-in Lua companion and a minimum-only Sigil requirement. Its three WASM exports and host-owned authority stay unchanged.
- Reuse bounded workflow helpers without adding a plugin API. Copy the version-pinned project Lua companion for one-shot Start plus bounded Describe polling, History traversal, completion payload selection, and explicit one-layer JSON decoding. Page, event and byte limits fail explicitly; exhaustion never becomes a workflow status or partial success.
- Keep compatible host upgrades independent of plugin releases. The manifest requires Sigil
>=0.35.0, exact Host API=1.3.0and schema 4, without the former evaluator minor ceiling. This preserves admission checks; it does not certify unmeasured future hosts. - Preserve payload and Start meaning. Ordered payload bytes and metadata remain intact, empty completion differs from missing completion, and
effect="applied"does not prove a newly created execution. Start remains single-shot with a 10-second ceiling; only close-event History permits 65 seconds. - Acceptance belongs to the measured tuple. CAPI accepted Sigil 0.35.1 with Temporal 0.1.1-rc.1 and the exact copied companion: five profiles, 10 scenarios, 319 unchanged assertions and both frozen expected-RED fingerprints. The addendum confirmed human-only identity guidance and independently checked denied-workflow absence; it did not rerun the full suite. This is RC evidence, not a separate stable-artifact CAPI run.
See Temporal 0.1.1 and the Lua companion.
[0.35.1] — 2026-09-10 — Clear Grants
Section titled “[0.35.1] — 2026-09-10 — Clear Grants”Plugin troubleshooting becomes more precise without changing authorization, guest error classification, replay policy, or Host API 1.3. Temporal plugin publication and real-caller acceptance remain separate release gates.
- Understand a denied gRPC call without exposing private values. Human
sigil runandsigil scenario runoutput now identifies closed profile, RPC-alias, and request-policy failure categories. The first failure remains sticky and the denied call stays unsent; JSON, ledger, replay, and agent-safe feedback omit the private hint. - Network-grant warnings recognize semantic endpoint use. Endpoints referenced by gRPC or SigV4 profiles no longer appear unused merely because the plugin lacks raw networking. Unused extras still produce a count-only warning without endpoint names or targets. Routes and raw-capability restrictions are unchanged.
- Compatibility tests keep admission separate from certification. New regressions exercise minimum-only evaluator requirements while retaining independent exact Host API and authority checks. They neither change admission rules nor certify future hosts or widen an existing plugin’s immutable manifest.
See pre-send denial guidance and official plugins.
[0.35.0] — 2026-09-10 — Semantic Plugins
Section titled “[0.35.0] — 2026-09-10 — Semantic Plugins”Sigil adds bounded structured-data tools and the host-side protocol boundary needed by the Temporal plugin candidate. The supporting host release and the plugin’s real-service acceptance remain separate gates.
- Decode JSON without a shell or extra capability.
sigil.json.decodepreserves exact signed 64-bit integers, explicit null, and empty array/object identity in immutable values. Duplicate keys, fractional numbers, malformed input, and resource overruns fail explicitly. - Traverse data without embedding another programming language.
sigil.dataprovides bounded selection, dynamic-key access, structural equality, filtering, sorted keys, and deterministic scalar collection operations. Small selectors handle paths and expansion; ordinary Lua callbacks handle computation. Decoderes.bodyfirst: existingres.jsonLua tables are not structured-data values. - Semantic gRPC keeps transport authority with the host. Host API 1.3 and manifest schema 4 add the three admitted Temporal workflow methods. Operator-owned profiles constrain requests, routes, TLS, lazy bearer credentials, deadlines, and byte ceilings; guests receive no credentials or generic RPC authority. Host API 1.0–1.2 remains unchanged.
- Replay does not silently authorize mutations. Ordinary replay verifies pinned evidence without service execution. Live replay refuses a selected semantic profile containing any mutation RPC before deployment, and baseline-verdict fields distinguish the two lanes.
- Generated components retain bounded admission. Core-control nesting has its own 128-frame ceiling while recursive component/type metadata remains capped at 32. Other structural ceilings remain unchanged.
- Concurrent work retains its own resources and protection. Per-evaluation deployment ownership avoids cross-run Compose collisions, while scoped redaction registrations cannot be removed by another operation. Ephemeral port handoff collisions still fail closed.
- Counterexamples stay in the right place. Direct operator runs retain their minimized file artifact, while evaluations keep structured evidence without writing
counterexample.jsoninto the caller’s checkout. Holdout runs never create a plaintext sidecar. - Temporal remains on its candidate track. This release supplies host support; it does not declare the separate Temporal plugin stable or its real-service replacement gate accepted.
See structured data, semantic gRPC, and replay behavior.
[0.34.0] — 2026-09-04 — Plugin Adoption
Section titled “[0.34.0] — 2026-09-04 — Plugin Adoption”Sigil makes locked plugins easier to inspect and compose against externally managed services, while preserving the boundaries that make their results trustworthy. The accepted 0.34.0-rc.1 code is promoted with only version and changelog changes.
- Published ports bind to the full plugin target.
--plugin-route SERVICE:LOGICAL_PORT=URL, structured--endpoints-fromentries, and[eval.plugin_routes]keep logical service identity separate from the port an orchestrator publishes. Legacy named routes remain valid when the ports match. - Unused plugins and absent routes stop blocking unrelated work. Plugin-free selections do not read the project lock or store; plugin-using selections freeze only their effective exact dependencies. An absent endpoint fails on first use as sticky
PLUGIN_NETWORK_DENIED, while supplied routes retain eager validation and selected incompatibility still fails closed. - Binary logs preserve the bytes you are debugging.
sigil.logkeeps UTF-8 as JSON strings and represents other byte strings with reversible base64, a byte count, and a BLAKE3 digest. A cumulative 1 MiB per-scenario budget fails explicitly rather than truncating; logs remain outside agent-visible eval feedback. - Plugin authoring has explicit inspection and test paths.
sigil plugin inspect NAME[@VERSION]verifies and reflects an installed package offline.sigil plugin test --path ... --scenario ... --allow-localexercises an explicitly authorized local candidate through the production host in disposable state, without project locks, ledger events, trust changes, or ALLOW decisions. - Shared helpers cannot hide plugin capability use from project lint. Bounded literal
require("lib.*")graphs attribute nested plugin loads to the calling scenario’s policy. Broken or cyclic graphs fail closed; helper code cannot grant authority. - Minor prereleases retain an explicit last-stable compatibility identity. The 0.34.0 candidate accepts plugins compatible with 0.33.2 without satisfying a requirement for final 0.34.0. Malformed or stale compatibility metadata fails closed.
- The official plugin set preserves temporal meaning. Codec
1.1.2, MySQL0.2.1, S30.3.0, and Parquet0.2.0form the current set. Parquet now preservesisAdjustedToUTCasis-adjusted-to-utcin structured metadata and time/timestamp cells, including an explicitfalse. - Acceptance retains the expected failures. Authority-bearing CAPI acceptance passed all five profiles across 10 scenarios and 319 assertions, with identical expected verdicts and both intentional RED assertions preserved. Browser parity regression coverage and its live CI gate were also hardened.
See plugin route setup, plugin author commands, binary logging, and Parquet 0.2.0.
[0.33.2] — 2026-09-03 — Typed External Data
Section titled “[0.33.2] — 2026-09-03 — Typed External Data”The typed-data plugin stack is now stable after repeated acceptance against an externally orchestrated CAPI service graph on Apple Silicon. Sigil supplies the routing, authorization, and bounded-pagination contracts without deploying the services itself.
- The accepted typed-data set is stable and reproducibly lockable.
mysql@0.2.1provides stateful typed SQL and complete MySQL 8 authentication,parquet@0.1.1provides bounded typed column and projected-row reads, ands3@0.3.0provides prefix-confined private GET/HEAD plus caller-driven ListObjectsV2 pagination. See Official Plugins. - Plugin routes preserve orchestrator-native endpoint names and schemes. Grant targets may use exact dotted keys such as
singlestore-pipelines.sql:3306; directmysql://routes have the same plugin-only semantics through--endpointand--endpoints-from, with no fallback to a base service. See Using Plugins → Direct runs and named network services. - Plugin secrets are lazy and still fail closed. Unused grants no longer block unrelated scenarios. A selected missing secret cannot be caught into a passing result; trusted human output names the required
--enventry while JSON, eval feedback, and ledger evidence omit the name and value. See CLI →sigil run. - The full stack is field-accepted. The CAPI operator ran all five profiles four times through the literal rig-owned composition, retaining 8 scenarios / 299 assertions on distribution and 2 / 20 on compute, both expected REDs, byte-exact private object reads, one persistent typed SQL session, and CAPI-736 RED at the known-bad ref and GREEN at the fix.
- Stable artifacts target Apple Silicon macOS and x64 Linux only. No Intel macOS artifact is built. Install Sigil 0.33.2.
[0.33.2-rc.2] — 2026-09-02 — External Data Remediation Candidate
Section titled “[0.33.2-rc.2] — 2026-09-02 — External Data Remediation Candidate”This candidate carries the four follow-ups from the first real CAPI migration
run. It remains a prerelease alongside stable 0.33.1 and does not advance the
stable installer, homepage version, Homebrew formula, or latest release.
- Plugin routes preserve orchestrator-native endpoint names. Grant targets may use exact dotted keys such as
singlestore-pipelines.sql:3306, with no fallback to the base service. Direct--endpoint sql=mysql://127.0.0.1:3306now has the same plugin-only semantics as an identical--endpoints-fromentry: it is excluded fromsigil.service(), HTTP origin pinning, and reset targets. See Using Plugins → Direct runs and named network services. - Plugin secrets are resolved when a selected operation needs them. An unused generic or SigV4 grant no longer aborts an unrelated scenario. A selected missing secret still fails closed and cannot be caught into a passing run; trusted human output names the exact missing
--enventry, while JSON, eval feedback, and ledger evidence omit the name and value. See CLI →sigil run. - The matching MySQL candidate completes the authentication surface.
mysql@0.2.1-rc.1handles cold and warmcaching_sha2_password, server auth switches, andmysql_native_password, and preserves authentication vendor code and SQLSTATE without retrying or reconnecting. Use it with Sigil 0.33.2-rc.2, unchangedparquet@0.1.1, and unchangeds3@0.3.0-rc.1. - The candidate awaits the repeated real-box gate. Portable checks and public provenance are complete; stable promotion still requires the CAPI operator to repeat the five-profile run without the former dotted-route alias and explicitly accept the exact public identities.
- Artifacts remain Apple Silicon macOS and x64 Linux only. No Intel macOS artifact is built.
[0.33.2-rc.1] — 2026-09-02 — Bounded Pagination Candidate
Section titled “[0.33.2-rc.1] — 2026-09-02 — Bounded Pagination Candidate”This prerelease makes the reviewed typed-data plugin set installable from
public immutable releases for final CAPI-box acceptance. It remains alongside
stable 0.33.1 and does not advance the stable installer, homepage version,
Homebrew formula, or latest release.
- SigV4 grants can authorize one bounded opaque continuation token. Host API 1.2 adds
opaque_max_encoded_bytesfor exact allowlisted query fields. The host rejects missing policy, non-ASCII input, malformed or noncanonical percent encoding, and encoded values over the declared limit before signing or I/O. Host-generated diagnostics and evidence do not automatically include the token; scenario Lua still controls any explicit logging or attachment of the returned value. See Using Plugins → host capabilities and grants. - The complete typed-data set is publicly lockable. Stable
mysql@0.2.0adds stateful typed SQL for SingleStore’s MySQL 5.7 dialect and MySQL 8, while stableparquet@0.1.1adds bounded column and projected-row reads. Prereleases3@0.3.0-rc.1composes caller-driven ListObjectsV2 pagination with read-only HEAD and GET. See Official Plugins. - Compatibility fails closed. S3 0.3.0-rc.1 uses manifest schema 3 and requires Sigil 0.33.2-rc.1 or newer; stable Sigil 0.33.1 rejects it rather than silently omitting Host API 1.2 policy.
- The candidate targets only Apple Silicon macOS and x64 Linux. No Intel macOS artifact is built. Install Sigil 0.33.2-rc.1; the separate real CAPI-box acceptance gate remains open.
[0.33.1] — 2026-09-01 — Field-Proven Plugins
Section titled “[0.33.1] — 2026-09-01 — Field-Proven Plugins”The complete plugin boundary was accepted against an externally orchestrated
CAPI service graph on Apple Silicon after four focused release candidates.
Stable 0.33.1 advances the installer, latest, and Homebrew while retaining
the smaller Apple Silicon macOS plus x64 Linux artifact matrix.
- Private S3 reads can be signed by the host. Bounded SigV4 grants bind named secrets, region, service, authority, methods, paths, query fields, and headers while the named endpoint remains the only socket route. Guest Lua names the grant but never receives credentials. See Using Plugins → host capabilities and grants.
- Plugin failures are safe and machine-readable. Direct-run JSON retains fixed
errorand source-freediagnosticstrings plusplugin_failure = {code, stage, operation?, message}without plugin identity, grants, routes, source chains, or secret material. Capability, secret, and host resource-policy failures override guestpcalland a guest PASS. See CLI →sigil run. - Externally managed service maps compose cleanly.
--endpoints-fromaccepts non-HTTP routes such asmysql://host:3306for locked plugins without adding them tosigil.service()or the HTTP origin-pin set; explicitly written default ports remain valid. - Plugin projects bootstrap without hidden generated state. The first official
plugin addcreates a minimal schema-linked project config, and authoring commands reconstruct missing generated stubs from an approved lock while refusing unknown state. - Argument and policy diagnostics are precise and deterministic. Malformed WIT values name the component function and parameter, fixed-record errors no longer depend on Lua iteration order, and selected-plugin uninstall refusal no longer masquerades as a version conflict.
- The release is field-accepted. The reporting CAPI box verified byte-exact private S3 GET and HEAD, Parquet handoff, all documented profile verdicts, exact resource ceilings, fail-closed caught failures, and clean targeted leak scans. Install Sigil 0.33.1 with the accepted
s3@0.2.0-rc.1package.
[0.33.1-rc.4] — 2026-09-01 — Diagnostic Completion Candidate
Section titled “[0.33.1-rc.4] — 2026-09-01 — Diagnostic Completion Candidate”This narrow candidate keeps s3@0.2.0-rc.1 unchanged and closes the one
reporting mismatch found during the rc.3 CAPI run. It remains alongside stable
0.33.0 and does not advance the stable installer, homepage version, Homebrew
formula, or latest release.
- Exact plugin failures always include a direct-run diagnostic.
sigil run --jsonnow emits the bounded source-freediagnosticalongsideerrorandplugin_failure, including when guestpcallcaught the immediate Lua exception. The diagnostic contains only the closed error code and its fixed operator summary, never a Lua code frame that could repeat plugin identity, grants, routes, secret names, or arbitrary source text. See CLI →sigil run. - The candidate still targets only Apple Silicon macOS and x64 Linux. No Intel macOS artifact is built. Install Sigil 0.33.1-rc.4 with the unchanged
s3@0.2.0-rc.1package.
[0.33.1-rc.3] — 2026-08-31 — Observable Boundary Candidate
Section titled “[0.33.1-rc.3] — 2026-08-31 — Observable Boundary Candidate”This candidate keeps s3@0.2.0-rc.1 unchanged and closes the remaining
operator-observability and service-map papercuts found during the rc.2 CAPI
run. It remains alongside stable 0.33.0 and does not advance the stable
installer, homepage version, Homebrew formula, or latest release.
- Plugin infrastructure failures are machine-readable in direct-run JSON. Exact typed failures retain their safe fixed
erroranddiagnosticand addplugin_failure = {code, stage, operation?, message}. The projection excludes plugin identity and version, routes, source chains, secret names, and secret values; agent-visible holdout feedback remains coarse. See CLI →sigil run. - Explicit default ports survive imported-route validation.
--endpoints-fromaccepts non-HTTP routes that spell their scheme default, includingws://host:80,wss://host:443, andftp://host:21. Omitting the port still fails before scenarios run. - Malformed fixed-record diagnostics are deterministic. An overfull plugin argument always reports the fixed-arity violation, while a same-size misspelled field always reports an unknown field, independent of Lua table iteration order.
- S3 body and wire limits are documented separately. The object ceiling reserves exactly 64 KiB of host response framing before I/O, request bytes need separate headroom, host policy refusal remains uncatchable infrastructure, and a typed object-body
limitremains catchable but never returns partial bytes. See Official Plugins → S3. - The candidate still targets only Apple Silicon macOS and x64 Linux. No Intel macOS artifact is built. Install Sigil 0.33.1-rc.3 with the unchanged
s3@0.2.0-rc.1package.
[0.33.1-rc.2] — 2026-08-31 — Route Boundary Candidate
Section titled “[0.33.1-rc.2] — 2026-08-31 — Route Boundary Candidate”This focused follow-up keeps s3@0.2.0-rc.1 unchanged and tightens the host
boundaries exercised by the first candidate. It remains a prerelease alongside
stable 0.33.0 and does not advance the stable installer, homepage version,
Homebrew formula, or latest release.
- Whole service maps can include non-HTTP plugin routes.
--endpoints-fromaccepts bare protocol entries such asmysql://127.0.0.1:3306for locked plugin routing without adding them tosigil.service()or the HTTP origin-pin set. Invalid entries retain the source path, offending key, and cause in the top-level diagnostic. See CLI →sigil run. - Plugin boundary refusals identify the layer that rejected them. Malformed Lua values now name the component function and WIT parameter as scenario-authoring errors; declared SigV4 secrets are checked against the scenario environment before component instantiation; and response ceilings beyond a network grant’s remaining wire budget are classified at authorization. Unknown grants, denied secrets, and host resource policy remain fail-closed and cannot be caught into a passing scenario.
- Signed authority and socket routing are separate by design. A named endpoint selects the host-owned socket route. A SigV4 grant’s
authoritysupplies and signs HTTPHost; it neither selects nor widens that route and need not equal the route name, supporting private routes, proxies, and virtual-hosted S3. MinIO accepting a self-consistent alternateHostand signature is therefore not a route escape. See Using Plugins → Direct runs and named network services. - The candidate still targets only Apple Silicon macOS and x64 Linux. No Intel macOS artifact is built. Install Sigil 0.33.1-rc.2 with the unchanged
s3@0.2.0-rc.1package.
[0.33.1-rc.1] — 2026-08-31 — Signed Delivery Candidate
Section titled “[0.33.1-rc.1] — 2026-08-31 — Signed Delivery Candidate”This prerelease is the cross-machine acceptance build for the host-signed
object-storage path. It is hosted alongside stable 0.33.0 and deliberately
does not advance the stable installer, homepage version, or latest release.
- WASM plugins can request bounded host-side SigV4 signing. Manifest schema version 2 adds an explicit
sigv4capability whose project allowlist binds secret names, regions, services, signing authorities, and clock skew. Credentials remain in the host; undeclared or ambiguous authority is rejected before I/O. - Patch candidates retain the preceding stable plugin ecosystem. Sigil
0.33.1-rc.1accepts plugins compatible with0.33.0and plugins that explicitly opt into the RC line, while refusing plugins that require the unreleased final0.33.1. - The matching S3 candidate exercises the complete route.
s3@0.2.0-rc.1performs bounded host-signed GET and HEAD requests against S3-compatible services, alongside the existing anonymous and presigned paths. Install the pair with the Sigil RC installer, then runsigil plugin add s3@0.2.0-rc.1in a project. - Release artifacts target Apple Silicon macOS and x64 Linux. No Intel macOS artifact is built. The RC remains excluded from Homebrew and stable-site announcement channels.
[0.33.0] — 2026-08-29 — Outside the Lane
Section titled “[0.33.0] — 2026-08-29 — Outside the Lane”Sigil can now compose locked network plugins against services that another tool already brought up. Direct runs resolve network authority only from explicit named services, while built-in HTTP and process paths preserve exact bytes and deterministic generators can produce one-off values without shell access.
- Network plugins work against externally managed boxes. A plugin grant targeting
minio:9000can resolve from--endpoint minio=http://127.0.0.1:PUBLISHEDor the same named service in--endpoints-from. The published URL port replaces the logical deployment port. A bare primary endpoint is never inferred as plugin authority, and missing, ambiguous, TLS-incompatible, or upgrade-only routes fail before reset hooks or scenario code. See Using WebAssembly Plugins → Direct runs and named network services. - HTTP and
sigil.execare binary-safe. Response bodies, stdin, stdout, and stderr arrive in Lua as exact byte strings, including invalid UTF-8 and embedded NULs. The existing 1 MiB process-output bounds remain byte-accurate; internal JSON evidence marks binary data with a boundedbase64-preview, byte count, and BLAKE3 digest instead of silently returning an empty or replacement-expanded value. sigil.gen.sample(...)produces deterministic one-shot data. Generator factories remain lazy descriptors forinvariant, while ordinary scenario code can sample UUIDs, emails, strings, integers, booleans, bytes, constants, and choices without grantingexec. Direct runners accept--seed <64-hex|auto>, record the selected root seed, reproduce the same sample stream, and do not perturb invariant case generation. See Lua DSL → Generators.- A reviewed plugin lock can bootstrap a clean clone without editor stubs.
plugin sync, lint, run, eval, and replay trust the exact checked-in lock independently of.sigil/types/wasm/. Sync never creates or mutates stubs;sigil generate-typesrecreates them when an editor needs them. Authoring commands retain transactional lock/stub protection. - Assertions in required helpers retain assertion classification. Module-body assertions, nested helpers, and helper-built
runfunctions now receive the same diagnostics andfailure_class = "assertion"as calls authored in the scenario file. A caught assertion followed by a real runtime failure remains a crash. - Dynamic scenario metadata fails closed as E009. Scenarios must return a literal top-level table so title, priority, budget, tags, and capabilities remain statically reviewable.
return helper.build()now aborts during lint/preflight before filtering, resets, plugin acquisition, deployment, or scenario execution; a literal table may still delegate itsrunfunction. - Malformed plugin names point at the CLI argument. Invalid
NAMEorNAME@VERSIONvalues are safely escaped and rejected before network access with the accepted syntax. Canonical SemVer errors and invalid fields inside actual package manifests remain distinct.
[0.32.6] — 2026-08-28 — Trusted Path
Section titled “[0.32.6] — 2026-08-28 — Trusted Path”Official plugin acquisition now follows the host’s TLS trust policy. This
brings sigil plugin install and sync in line with browsers and tools such as
curl on enterprise networks that install an interception root, without
weakening package integrity or provenance checks.
- Plugin downloads work through trusted enterprise TLS inspection. GitHub API, release asset, redirect, and attestation-bundle requests use the platform verifier backed by operating-system trust roots, so an installed Netskope or other enterprise root is recognized while certificate-chain and hostname verification stay mandatory. Exact package checksums and Sigstore provenance are still verified independently after download.
- Transport failures are diagnosed accurately. A rustls certificate failure wrapped by the HTTP stack as an I/O error remains
PLUGIN_TLS_TRUST_INVALID; DNS resolution, invalid proxy configuration, connection refusal, timeout, and other transport failures carry safe, specific causes without exposing credentials or response bodies.
[0.32.5] — 2026-08-27 — Data Paths
Section titled “[0.32.5] — 2026-08-27 — Data Paths”Sigil’s official plugin catalog now covers the practical object-data path: fetch bounded bytes from an S3-compatible store, then inspect a single typed value in a Parquet file. This patch also fixes host-command capture for scenarios whose commands produce substantial output.
- Official S3 and Parquet plugins.
s3@0.1.0performs a bounded, read-only object GET from S3-compatible services including MinIO, whileparquet@0.1.0reads metadata or one typed scalar cell from the resulting bytes. They are independent project dependencies: only S3 receives network authority. See the new Official Plugins catalog for install commands, grants, usage snippets, and current limits. sigil.execdrains output while commands run. Stdout and stderr can no longer fill an OS pipe and deadlock the scenario. Each stream retains its first 1 MiB, reports discarded excess throughstdout_truncatedorstderr_truncated, and keeps draining until the command exits.- Host command process groups are cleaned up. Each
sigil.execcall starts in an isolated process group; timeout, Ctrl+C, output failure, and normal return terminate processes that remain in that group, including ordinary background commands that inherit it. This is lifecycle cleanup, not a security boundary: a command that deliberately creates a new session can leave the group.
[0.32.4] — 2026-08-26 — Clean Room
Section titled “[0.32.4] — 2026-08-26 — Clean Room”Use this release for new 0.32 installs. It is the same complete Component Model plugin system introduced in 0.32.0, followed through clean hosted builds and the real Docker-backed evaluation path before publishing the final artifacts.
- Plugin-free selections stay plugin-free. A project may declare plugins for other scenarios without forcing every core-DSL run to have those unrelated packages in its user store. The moment a selected scenario uses
require("wasm.NAME"), the checked-in project requirement, exact lock, immutable package, and scenario capability are still mandatory and fail closed on any mismatch. - The full workspace gate runs in a bounded clean job. Hosted tests have their own runner and cache, disable test debug symbols, and serialize large integration-test link steps. The current-layout fixture setup and Docker evaluation pipeline now run from a clean checkout, so release validation covers the path users actually install.
[0.32.3] — 2026-08-26 — One at a Time
Section titled “[0.32.3] — 2026-08-26 — One at a Time”The complete hosted test suite was still able to launch several large linkers at once and exhaust the runner even though product tests were passing.
- Workspace test linking is serialized. Cargo uses one build job for the complete workspace test gate, removing the runner-memory race without dropping or weakening tests.
[0.32.2] — 2026-08-26 — Separate Circuits
Section titled “[0.32.2] — 2026-08-26 — Separate Circuits”The first stabilization patch exposed two CI-only races that did not belong in the plugin runtime: concurrent feature-matrix linking and a command-judge test that could answer before consuming its prompt.
- The plugin feature matrix has its own runner. Plugins-on and plugins-off checks, tests, and strict Clippy no longer compete with the default workspace linker; both jobs independently gate the release.
- Command-judge tests preserve the production pipe contract. Test providers consume stdin before responding, so they no longer race prompt delivery while production still treats a broken pipe as a fail-closed provider error.
[0.32.1] — 2026-08-26 — Green Matrix
Section titled “[0.32.1] — 2026-08-26 — Green Matrix”The WebAssembly plugin release compiled and passed its 2,509 plugins-disabled tests, but the first release-commit CI run then found one strict-Clippy failure in that feature matrix: the no-op plugin preflight deliberately retained a fallible signature shared with the feature-enabled implementation. This patch documents that interface choice with a narrow lint allowance. Runtime behavior and the plugin format are unchanged.
- Plugins-disabled builds are green under strict Clippy. Shared scenario-run callers still propagate real lock and freeze failures when plugin support is enabled; the disabled build remains a no-op and now passes the same complete CI feature matrix used before release.
[0.32.0] — 2026-08-26 — Plugged In
Section titled “[0.32.0] — 2026-08-26 — Plugged In”Sigil can now run WebAssembly Component Model plugins as reproducible project
dependencies. A scenario loads require("wasm.codec"); the checked-in project
requirement, exact lock, package digests, publisher evidence, host grants, and
runtime limits determine what executes in both evaluation lanes and later
replay. Public Codec 1.1.2 and MySQL 0.1.2 packages exercised that complete
install → lock → dual-lane evaluation → offline replay path before release.
- A bounded
wasm.<name>Lua module system. Plugin functions and WIT records, tuples, lists, options, results, flags, enums, variants, constructors, resources, and methods cross a closed typed boundary. Each scenario/environment lane gets a fresh Lua VM, Wasmtime Store, module cache, quotas, and resource table; memory, value shape, component complexity, fuel, time, calls, instances, and resources are bounded. Cancellation and teardown release retained resources without running untrusted cleanup. See Using WebAssembly Plugins. - Project dependency commands:
sigil plugin addandremove.add NAME[@VERSION]installs when necessary, writes an exact formatting-preserving[plugins.require]entry, and transactionally refreshes.sigil/sigil.plugins.lockplus managed LuaLS stubs.remove NAMEremoves project authority, lock entry, and stub while retaining cached bytes. Upgrade note: an installed orcurrentuser-cache version is no longer enough in any execution mode; existing store-only workflows must runsigil plugin add NAME. - Deterministic package management and CI sync.
pack,validate,inspect,install,list,list-remote,info,verify,use,update, anduninstallcover bounded canonical archives and official or explicitly enabled third-party GitHub releases.plugin lockresolves reviewed requirements;lock --update NAMEmakes a source change explicit;plugin syncinstalls only exact identities already approved by the lock. See the CLI reference. - Operator-owned host capabilities, no ambient WASI. Plugins can request bounded logging, deterministic random, separately authorized entropy, named secrets, and named outbound TCP/TLS endpoints. Effective authority is the intersection of the manifest request, publisher policy, control-ref grant, scenario capability, and operator denylist. Components receive no filesystem, process, environment enumeration, clock, stdio, raw DNS, listener, UDP, or arbitrary Internet surface. See Configuration → Plugins.
- Keyless provenance for official packages. Sigil verifies the exact package plus GitHub repository, commit/ref, protected workflow/environment, trigger, hosted runner, SLSA predicate, and public-transparency identity, then pins the selected bundle and trusted root for offline evaluation, replay, and decisions. Historical digest-only packages remain diagnostic and cannot authorize a fresh ALLOW; missing or drifted plugin evidence fails closed.
- Plugin evidence is durable but feedback stays lossy. Reports and
eval.completebind exact trusted plugin identities and content-addressed package/component/proof blobs; attestation predicate v2 signs only the coarse plugin-infrastructure marker. Replay is blob-first and independent of the network or mutable cache selection, while agent-visible feedback never exposes plugin identities, lanes, scenario names, or counts. - Clear plugin-manager outcomes. Human
plugin installoutput starts withInstalled NAME@VERSION;infoandverifyusePlugin …andVerified …headings, and inactive versions name the actual cache selection. JSON output is unchanged.
[0.31.0] — 2026-08-19 — Second Report
Section titled “[0.31.0] — 2026-08-19 — Second Report”Another batch from the same external agent, all about the project-less runner being harder to talk to than it should be. The bug first: sigil run inferred capabilities from the literal call sites in a scenario file and let that inferred set replace the declared policy.capabilities, so exec declared but called from a lib/ helper hit the denying stub. Then legibility: sigil.log and sigil.attach show up in the runner’s output, bare expect takes a message, unknown scenario keys warn, and I001 stops telling expect.eq-only scenarios they have no assertions. One lint tightening to read before upgrading: strict lint (sigil scenario lint, promote) now sees a capability call whose result is indexed inline (sigil.exec("x").status), so a committed scenario using that shape without declaring the capability newly fails E003 — it was a runtime denial waiting to happen.
sigil runno longer strips declared capabilities. The effective set undersigil runis now declared ∪ inferred: a declaration is always honored, inference only adds (so a bare{ run = … }still works), and--deny-capabilitystill wins over both. A capability reached through arequire('lib.x')helper,sigil["exec"], or alocal f = sigil.execalias must be declared — inference only sees literal call sites in the scenario file. See Writing scenarios → Capabilities.expect(cond, message)— bareexpecttakes an optional second argument, a string or{ message = "…" }, appended to the failure text and used asexpects[].descriptionwhen no---doc comment precedes the call. See Lua DSL →expect.sigil.logandsigil.attachsurface insigil run/sigil scenario run.--jsoncarrieslogs: [...]andattachments: {name: value}on every scenario entry (always present); human mode printslog:lines to stderr as they happen andattach:lines after the scenario.sigil eval’s lossy feedback and the PR comment are unchanged. See Lua DSL →sigil.log/sigil.attach.- W008 — unknown scenario metadata keys.
timeout,timeout_ms,budget_msand typos of real keys were silent no-ops; they now warn with a near-miss hint pointing atbudget = { max_seconds = N }, in both lint modes and undersigil run. - Capability inference sees a call result indexed inline —
expect(sigil.exec("x").status == 0)in a policy-less scenario now infersexecinstead of being denied; the same fix feeds E003/E007. - I001 (“no assertions”) counts
expect.<method>(),invariant(), andsigil.check(), not only literalexpect(.
[0.30.0] — 2026-08-19 — Meeting in the Middle
Section titled “[0.30.0] — 2026-08-19 — Meeting in the Middle”The follow-up to Running Boxes. 0.29.0 gave sigil run named services and --endpoints-from, and gave the configured modes per-service reset hooks — but not to each other, so a box sigil did not deploy still could not be reset from a project-less run. 0.30.0 closes that gap, and fixes a pinning hole the same tool-fed shape exposed. One behavior change to read before upgrading: a sigil run that declares only named services (no bare --endpoint) is now pinned to those origins instead of running unpinned; a run that declares no origins at all is unchanged.
sigil run --reset [NAME=]METHOD:/pathand--resets-from <json>— reset hooks for the project-less runner.--reset(repeatable) sends an HTTP request before every scenario, to the primary--endpointor, withNAME=, to a named service declared with--endpoint name=url/--endpoints-from; any 2xx is success.--resets-from(-reads stdin) loads a JSON array whose entries carry the same fields as a[[scenario.reset]]table —method,path,service,headers,body,expected_status— for hooks that need more than a method and a path. Validated before the first scenario runs with the same rules[scenario.reset]gets at config load; a hook that fails at run time fails that scenario without executing its body (failure_class = "crash"). Hooks only reach the primary or a declared service with a relative path, so they add no pinning surface. See Quickstart: Test a Running Service → Reset between scenarios and CLI →sigil run.- A services-only
sigil runis now pinned. Origin pinning was inert whenever no bare--endpointwas passed, even with named services declared — so the tool-fed multi-twin shape ran with pinning off and anyopts.base_urlcould reach any origin. A non-empty declared-origin set now activates pinning on its own: the run is confined to the union of the--endpointorigin (when given), every named service origin, and every--allow-originvalue, for requests and 3xxLocationtargets alike.--allow-originstill widens the set,--allow-cross-originstill disables pinning, the single-origin diagnostics are unchanged byte-for-byte, and blocks reportfailure_class = "pinning". sigil ciexplains a policy-mode cap. Onlyautocan ever ALLOW, so a clean eval undershadow,advisory, or an unset mode returns REVIEW — andsigil ciused to render that exactly like a failed review. The commit status now readssigil: REVIEW — clean (8/8 passed); capped by shadow mode (ceiling: review), the PR comment carries theshadow_mode_caprationale, and--format jsongainspolicy_mode,mode_ceiling,mode_capped,mode_cap_reason. A REVIEW with real findings keeps its counts and carries no cap vocabulary; exit codes and status states are unchanged. See CI integration → Branch protection.- A missing
require('lib.x')helper names the directory it was looked for in. Undersigil runthe error now appends the anchor it resolved from — the directory argument the scenario was found under, or the file argument’s parent — and suggests--lib-dirwhen it was not already given; an explicit--lib-dirfailure names that directory instead.failure_classstays"crash". See Whererequire('lib.X')resolves. sigil initcommented the policy table as# [[policy.{service_name}]](array-of-tables syntax for what is one table per service); it is now# [policy.{service_name}].- Docs: Configuration audited against the published schema.
[eval]is filled out (runs_per_scenario,confidence_level,pass_threshold,progressive,differential, budgets,max_eval_age) and the previously undocumented[holdout],[feedback],[security],[ledger]— home of themax_staleness_for_allowfreshness gate —[attestation],[observability],[browser], and[risk]tables have sections. Every TOML block on the page is now validated against the schema by a drift check in this site’s build tooling, so the page cannot quietly diverge again.
[0.29.0] — 2026-08-19 — Running Boxes
Section titled “[0.29.0] — 2026-08-19 — Running Boxes”A release shaped by someone pointing sigil at a box that was already running rather than at a PR: several twins on one host, each with its own reset URL, fed from a tool that already knows the service map, run in CI where third-party Lua must not get a shell. Plus the first published JSON Schema for sigil.toml, a config-side sigil.env() allowlist for evals, and the fixes that had landed since 0.28.0 without release notes. Two behavior changes to read before upgrading, neither of which affects a config that does not set the fields involved: an eval with [scenario.reset] hooks configured now sends them (and refuses hooks combined with scenario_concurrency > 1), and teardown plus the container / kubernetes start commands are now time-bounded. Nothing is denied, reset, or bounded differently by default otherwise.
sigil run --endpoints-from <PATH>loads named--endpointservices from a flat JSON file (-reads stdin) —rig env --format json | sigil run scenarios/ --endpoint http://a:8080 --endpoints-from -. Every key becomes a named service, validated and folded into the origin pin set exactly like--endpoint name=url; the primary endpoint stays argv-only, a name declared in both is an error, and a bad file, bad JSON, or bad origin aborts before any scenario runs. See Quickstart: Test a Running Service.[[scenario.reset]]— several reset hooks, each able to target a named[eval] service. A box with more than one twin resets each of them before every scenario. The single-table form parses exactly as before; hooks fire in declaration order, the first failure fails that scenario without sending the rest,pathstays relative per target so a reset can never leave the pinned origin set, and an undeclaredserviceis a config error listing the declared names. See Configuration →[scenario.reset].sigil evalfires[scenario.reset]hooks — before each scenario, PR deploy then baseline deploy, named-service hooks resolving through[eval] servicesfor both. A failed reset is that side’s setup failure: its scenario body does not run, the other side is untouched, and the pair reads as a regression, never ALLOW. Hooks plus[eval] scenario_concurrency > 1is refused before anything deploys rather than quietly downgraded to sequential. (sigil rungot its own--reset/--resets-fromflags in 0.30.0.)- Capability denylist.
[eval] denied_capabilities = ["exec", …]and repeatablesigil run --deny-capability <NAME>. Nothing is denied by default; an unknown name is a hard error. Enforced fail-closed at three layers — lint errorE007on both thepolicy.capabilitiesdeclaration and every call site, a runtime denying stub installed regardless of what the scenario declared, andsigil.intenttool exposure — and a blocked scenario reportsfailure_class = "capability". The motivating case:sigil.execrunssh -con the host running sigil, not inside the deployed container, so deny it wherever third-party or agent-authored scenarios run. - JSON Schema for
sigil.toml.sigil schema configprints the schema for the whole supported configuration, generated from sigil’s own config types and drift-tested in CI, and it is published at runsigil.com/schemas/sigil-config.schema.json.sigil initwrites a#:schemaheader pointing there, so editors with TOML schema support offer completion and flag unknown keys with no per-workspace setup. See Configuration. [scenario.env]—sigil.env()now works undersigil eval. Previously eval hardcoded an empty allowlist and everysigil.env(KEY)returned nil; onlysigil run --envpopulated it, and an earlier version of this site documented a[scenarios.env]table that had never been implemented.[scenario.env]is a strict per-key allowlist mirroringsigil run --env:KEY = "literal"orKEY = { from = "PROCESS_ENV_NAME" }for a passthrough that keeps secrets off disk, read from the control snapshot so a PR cannot widen its own allowlist. See Configuration →[scenario.env].[deploy] teardown_timeout(default 600s) bounds teardown, which previously ran with no time bound. Separate fromstartup_timeouton purpose — start fast, tear down patiently. On expiry the process is killed and reaped and a greppableSIGIL_TEARDOWN_LEAKwarning names the environment so an operator can reclaim anything left behind.containerandkubernetesbackends honor[deploy] startup_timeout. Both shelled out to a blockingdocker run/kubectl applywith no bound, so a wedged pull or stuck apply hung the eval with no decision. A timeout is now a fail-closed failure, as it already was forcompose.sigil eval --judge-modelactually reaches the judge. The flag was applied to the working-checkout config but the judge is built from the control snapshot, so it silently had no effect. It is now an explicit override applied to the snapshot — CLI flag beats both configs — and the provenance table recordsjudge.modelascli-flag.- git subprocess output no longer leaks onto stdout.
git worktree addprintedHEAD is now at …to the inherited stdout, sosigil eval --format jsoncould hand a machine consumer a git line prepended to the JSON document. The git helpers now capture both streams and re-emit them on stderr. sigil trustandsigil decideagree on an unset policy mode.trust showrendered a missing[policy] modeasadvisorywhiledeciderendered it asunset; both capped at REVIEW but disagreed on the displayed state. Both now renderunsetwith its REVIEW ceiling; explicit modes and exit codes are unchanged.- Docs: a second quickstart — Test a Running Service — the no-config
sigil run --endpointpath; thesigil.execreference and LuaLS type stub now match the runtime ((command, opts{cwd, env, stdin}) -> { stdout, stderr, status }); and this configuration reference was reconciled with the real config keys ([deploy],[policy.<service>],default_service).
[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