Changelog
Every release of Sabline, newest first, one page for each major version. The text is CHANGELOG.md's.
This project was called Velaris until 8.6.0; every entry below 8.6 uses the name it had at the time, which is what the record is for. docs/renamed.md says what moved where.
api: sabline conformance takes --runtime python|rust (9.0, M2): with
rust, sabline-rt answers the case kinds it implements and every result
says which runtime answered it; without the flag nothing changes.
api: a run takes --audit-stream FILE, and sabline.run, Pool.run and
PoolRegistry.run take audit_stream= (9.0, decisions/0007 32b): the run's
audit stream, sabline.audit-stream/1, one JSON object an event as it
happens - written to FILE a line each, or handed to the function. Without
either nothing changes.
api: SABLINE_REFERENCE_RUNTIME=rust sends every run through sabline-rt
(9.0, M3; the variable MAINTENANCE.md reserved) - sabline run,
sabline.run with and without timeout= and max_memory_mb=,
Pool.run, PoolRegistry.run (and so the HTTP door's and the MCP
server's runs), sabline replay and sabline test. Unset, or python,
nothing changes; any other value is refused - exit 2 on the command line,
a ValueError from the library and a pool - where until now nothing read
it. A call to the network, Python or a tool is relayed back to the package,
which makes it (decided on 2026-10-11); an eval run and sabline trace are
the package's, and say so in one line on stderr. From 9.0's M4 sabline-rt
asks the operating system to hold the budget in its own process, and a run
asked for a level of confinement is its own. sabline/through_rt.py says
what a run through sabline-rt is and is not.
api: an organisation ceiling (9.0, decisions/0007 32c): a file whoever
administers a machine installs - /etc/sabline/ceiling.json,
/Library/Application Support/Sabline/ceiling.json, or Sabline\ceiling.json
in Windows' ProgramData - holding sabline.ceiling/1, whose allow caps
what any budget on the machine may grant. A budget that goes past it is
refused before the program is read, with the new code E325, naming
the file, the first grant past it and what it allows; so is every run while
the file cannot be read as one. SABLINE_CEILING=FILE holds a run to one
more, never instead. A receipt's run_parameters gains ceilings, each
{"path", "sha256", "allow"} (empty where none is installed);
sabline ceiling [--json] shows what is installed; sabline audit names
it. Budget.gap is the rule it holds a budget to, which Budget.covers
(a door's --max-allow) now words. Where no ceiling is installed nothing
else changes. docs/ceiling.md.
api: SABLINE_THROUGH_RT_LOG=FILE, with SABLINE_REFERENCE_RUNTIME=rust,
has each run append one JSON line to FILE saying who answered it -
sabline-rt, the checks, or the package and why (9.0, M3; what
suites_through_rt.py counts). Without it nothing is written.
api: sabline run --confine strict, and confine="strict" for
sabline.run, Pool and PoolRegistry (9.0, M4; decisions/0006 section
e): a granted ffi module widens nothing the operating system holds, so
the policy is the budget's fs: and net: grants whatever modules are
granted, and a module's own native code, ctypes calls and processes are
held as the runtime's are. The OS policy document gains "strict": true
under it. Without it nothing changes. docs/confinement.md.
api: sabline run --require-confinement full|partial, and
require_confinement= for sabline.run, Pool and PoolRegistry (9.0,
M4; decisions/0007 item 32a): a run whose confinement, once applied, is
below that level is refused before its first statement with the new code
E326, and its receipt records the level it got. Beside --no-confine
or confine=False either is a usage error. sabline.CONFINE_STRICT and
sabline.REQUIRE_CONFINEMENT join the package's names, as CONFINE is.
Without either nothing changes.
api: with SABLINE_REFERENCE_RUNTIME=rust, sabline-rt asks the operating
system to hold the budget in its own process (9.0, M4), where until now a
run through it asked for nothing and its receipt said so. It applies the
same policy with the same layers - Landlock and seccomp-bpf on Linux, a
sandbox profile on macOS, the job object and the token's privileges on
Windows, with the low integrity level for a budget that grants no write -
and records the level, the reason, the layers and the policy's digest in its
own receipt. --confine strict and --require-confinement are carried in
its request and answered there (E326), so neither keeps a run in the
package any more. The command's own process is held to the same policy once
sabline-rt has been started, because a call to the network, a granted
Python module or a tool is made there. Three things follow for a run through
sabline-rt: a sabline.run() with no limit can be confined, since the
program runs in another process where the package's runs in the caller's; a
run whose host is not confined - an unbounded sabline.run()'s - says so in
its receipt's reason and is partial rather than full; and from M4's third
checkpoint a Windows run is held by an AppContainer of its own, so a
budget that restricts both directions and names no loopback host is full
there. docs/confinement.md and docs/os-enforces.md.
api: sabline serve and sabline mcp take --confine strict and
--require-confinement full|partial (9.0, M4), beside the --no-confine
they already took: the operator's own, on the command line, carried to every
pool the door makes. A request names a budget and never these. Either beside
--no-confine asks for two opposite things and the door does not start.
sabline eval refuses both by name, as it refuses --no-confine:
confinement is part of its profile and has no switch; its run goes through
sabline-rt from M4, and the stop file it needs is honoured there.
fix: sabline.run(source) through sabline-rt on Windows wrote the source to
a temporary file in text mode, so the receipt's subject digest was of bytes
with a carriage return before every line feed - not of the text that was
passed in. The file is written with the text's own bytes now. Receipts made
by the Python runtime, and by either runtime from a file on disk, were never
affected.
compatibility: E326 is new, and given only to a run whose operator asked
for a level of confinement; no run that does not ask is refused by it. On
Windows the Python runtime holds neither reads nor the network, so full
refuses every budget that restricts either - io does - where a run through
sabline-rt is held by an AppContainer and is full; and on macOS every run
is partial at best, so full refuses them all. docs/os-enforces.md has the
table, for both runtimes.
compatibility: for 9.0's Python package, the native engine is never
different from the interpreter, as it was always meant to be. A number past
64 bits, or a read outside a list or a text, stops the machine code where it
happens and the interpreter runs the call again: E407 and E602 now carry the
interpreter's words and line - the operator, the first read outside, the
list's or the text's own fixes - where they named neither, at the
function's line, and said "get" for a text. And and and or short-circuit
in native code as they do interpreted, so a program whose right side the
left one guarded no longer stops natively where it runs interpreted, and a
loop that only E407 ends no longer runs forever natively. fuzz_native.py
compares the two engines' stderr now, so it stays so.
compatibility: a function value is type checked once, in both runtimes,
where until now one that captured nothing was checked a second time. A
program's refusals are unchanged - sabline check reported each problem
once already - but the check document check_agreement.py compares
(sabline check --dump, each stage's problems before they are made one
to a message) no longer repeats a problem of such a value. Nesting is why
it matters: checking a value written inside a value checks the one inside
that, so a chain of N nested values was checked N times over, and the
parser fuzzer found a 23 KB program whose check document was 120 MB of
the same few problems - a minute of work for both checkers, where it is
now a fifth of a second. agreement_checks.py's
fn-values-nested-deeply holds the two runtimes to it.
9.x: 9.0.0-alpha.13 to 9.0.0-alpha.8 #
- 9.0.0-alpha.13 - M4: the Windows AppContainer, and sabline eval through sabline-rt
- 9.0.0-alpha.12 - M4: sabline-rt confines its own runs, and the command that starts it confines itself
- 9.0.0-alpha.11 - M3: the network, Python and tools relayed to the package, and the organisation ceiling
- 9.0.0-alpha.10 - M3: every run through sabline-rt, the limits held by the caller, and the suites counted per question
- 9.0.0-alpha.9 - M3: sabline run through sabline-rt, and what is written out counted first
- 9.0.0-alpha.8 - M3: receipts and the audit stream, field for field
9.x: 9.0.0-alpha.7 to 9.0.0-alpha.1 #
- 9.0.0-alpha.7 - M3: the builtins under every budget, as far as they got
- 9.0.0-alpha.6 - M3: the interpreter, as far as it got
- 9.0.0-alpha.5 - M2: the checkers, the budget parser, and a loop bounded by its input
- 9.0.0-alpha.4 - The job that was read, and then run
- 9.0.0-alpha.3 - The crate is published by a workflow of its own
- 9.0.0-alpha.2 - The parser, twice, and a release that publishes
- 9.0.0-alpha.1 - The parser, twice
8.x: 8.8 to 8.7.0 #
8.x: 8.6 to 8.5 #
8.x: 8.4 to 8.3.1 #
8.x: 8.3 to 8.2.1 #
8.x: 8.2 to 8.0 #
- 8.2 - Held to it
- 8.1.1 - The proof cache can no longer lie
- 8.1 - Receipts, and what a policy can ask
- 8.0 - What the socket reaches, and other things the audit now says
7.x #
- 7.2 - Releases that tag themselves
- 7.1.2 - The proof cache could be lied to
- 7.1.1 - 7.1.0 did not import on Python 3.10 or 3.11
- 7.1 - What an upgrade gained
- 7.0 - A secret you cannot look at
6.x #
5.x #
4.x: 4.4 to 4.2.1 #
- 4.4 - A platform that lets its customers write code it can audit
- 4.3.4 - The npm wrapper picks the right Python, and says when it cannot
- 4.3.3 - velaris mcp, so the npm package can start the server too
- 4.3.2 - The card's true size, and a registry manifest on the current schema
- 4.3.1 - A discount that cannot go negative, and a timeout that says it timed out
- 4.3 - Money, which is not a float
- 4.2.1 - The author name, capitalised correctly
4.x: 4.2 to 4.0 #
- 4.2 - A producer for the capability predicate
- 4.1 - Conformance you can run, provenance you can check
- 4.0.1 - The review read the old files from the wrong place
- 4.0 - The operator sets the limits, and the capability surface cannot widen quietly
3.x #
- 3.4 - The doors are locked, and the findings go where findings go
- 3.3 - The capability check now means what the spec says
- 3.2 - The capability format, published as a spec
- 3.1.1 - A pool test that passed for the wrong reason
- 3.1 - A pool that keeps its budget, memory caps on Windows, and a lockfile
- 3.0 - Budgets that name paths, hosts, counts, and secrets
2.x: 2.63.1 to 2.40.1 #
- 2.63.1 - The memory cap claim, narrowed to where it holds
- 2.63 - What a security reviewer looks for
- 2.62 - Loops that provably end, and a corpus built to fool it
- 2.61 - A table anyone can rerun
- 2.60 - The ffi cliff becomes a permission
- 2.59 - Time and memory limits, prompted by a review bot
- 2.58 - Ready to submit to the frameworks
- 2.57 - --strict, from a question on r/Compilers
- 2.56 - Seamless means tested, not claimed
- 2.55 - Four more doors
- 2.54 - A door for languages that are not Python
- 2.53.1 - The library suite knows what needs the prover
- 2.53 - Setting it up should not be a chore
- 2.52 - Velaris from inside other programs
- 2.51 - Smaller per-call costs, and an honest note about the ceiling
- 2.50 - Function values carry their surroundings
- 2.49 - The card's gaps were hiding three real bugs
- 2.48 - Speed, without touching a single guarantee
- 2.47 - The periphery round
- 2.46 - Contents, not just lengths
- 2.45 - The road from 84
- 2.44 - Everything the adversarial report found
- 2.43 - The prover crosses the loop boundary
- 2.42 - Findings from a model that installed it and tried to break it
- 2.41.2 - A loop no longer hides a divide by zero
- 2.41.1 - The card worked, and the first program it produced found a bug
- 2.41 - Written by a model, audited by you, run in a box
- 2.40.1 - Leading with the sandbox
2.x: 2.40 to 2.0 #
- 2.40 - Running a program you have not read
- 2.39.1 - The refusal harness knows what needs the prover
- 2.39 - The editor asks for what the compiler already knew
- 2.38 - Testing the places languages break
- 2.37 - A stress test written in Velaris
- 2.36.4 - The Action has its own name
- 2.36.3 - A picture of the point
- 2.36.2 - Somewhere to start
- 2.36.1 - Two-part tags publish the extension again
- 2.36 - A tool worth running, and errors that read like a language
- 2.35 - Dates as values, HTTP with headers, and a way in for others
- 2.34 - Unattended work, and building for machines you do not have
- 2.33 - A number a team can watch, and an image to run it in
- 2.32 - The editor knows what is proven, and CI is one line
- 2.31 - A standard library that reaches outside
- 2.30 - The language reference, and a position on concurrency
- 2.29 - Remembered proofs
- 2.28 - velaris build: hand someone your program
- 2.27 - Handles: real libraries, not just functions
- 2.26 - JSON, and calling Python with real data
- 2.25 - Reaching the outside world, visibly
- 2.24.1 - The extension follows the language's version
- 2.24 - Lists of text, and split
- 2.23 - Libraries you can actually share
- 2.22.1 - First extension publish
- 2.22 - The editor extension, ready to publish
- 2.21 - Watching a program run, and case-changing proofs
- 2.20 - Whole numbers have a size, and outgrowing it is an error
- 2.19 - pip install velaris-lang
- 2.18.2 - Minimal-mode expectations for the newest proofs
- 2.18.1 - Releases stay green
- 2.18 - Records holding lists, and publishing
- 2.17 - for loops, tests in Velaris, and text containment proofs
- 2.16 - Catching the next one, a third app, and a current tutorial
- 2.15.1 - Native text building, made portable
- 2.15 - Native text building (the arena)
- 2.14 - Native text reads, and proven functions run fast
- 2.13 - Native lists
- 2.12 - Lists of lists, proven
- 2.11 - Invariant inference (the boring ones, for free)
- 2.10 - Contracts on function values
- 2.9 - Map proofs
- 2.8 - A second real app, and Text ordering
- 2.7 - Reading a codebase
- 2.6 - Division proofs, and seeing what your code promises
- 2.6 - Division proofs
- 2.5.1 - One problem, one message
- 2.5 - Namespaced imports
- 2.4 - The everyday things
- 2.3 - Public launch polish
- 2.2 - Out-of-the-box readiness
- 2.1 - Documentation site
- 2.0 - The builtins keep the language's promise (BREAKING)
1.x #
- 1.20 - sort_by + ledger reports
- 1.19 - Standard library sprint
- 1.18 - Float proofs (real IEEE-754)
- 1.17 - Failure-aware proofs
- 1.16 - Quantified list proofs
- 1.15 - Native Float and Bool
- 1.14 - Record proofs
- 1.13 - The first real app
- 1.12 - Continuous integration
- 1.11 - Language server
- 1.10 - Formatter
- 1.9 - REPL
- 1.8 - Real installation
- 1.7 - Generics + first stdlib
- 1.6 - First-class functions
- 1.5 - Unignorable failure
- 1.4 - Maps
- 1.3 - Float
- 1.2 - Browser playground
- 1.1 - Escapes + editor
- 1.0 - Testers' release