Changelog: 4.x: 4.2 to 4.0
Every release of this major version, newest first, as CHANGELOG.md records it.
4.2 - A producer for the capability predicate #
A minor version. 4.1 published an in-toto predicate type for the audit
and wrote no Statements of it; velaris attest writes them, and the
release workflow signs one. The author's name is corrected everywhere
it appears.
velaris attest <path> [--output FILE] [--json] writes an in-toto
Statement v1 of the predicate type
https://gowrishankar-infra.github.io/velaris-lang/capability/v1
(velaris-spec 0.5, section 8.5). Its subjects are the audited file and
every file it imports, each by the sha256 of its bytes - a standard
library file named <stdlib>/NAME, since where it sits on disk is the
machine's business. Its predicate is the published shape: producer
(velaris-lang and this repository), specification (velaris-spec 0.5), auditedAt - from SOURCE_DATE_EPOCH when it is set, so one
commit gives the same bytes twice - and audit, which is audit()'s
own velaris.audit/1 document for those bytes, not a copy recomputed
beside it: effects, fs_paths, net_hosts, ffi_modules, ffi_any,
counts, proven_share, prover and velaris_version are the
audit's. It sets no conformance claim, since it runs no corpus. A
directory gives one Statement per .vel file, found as velaris capabilities finds them, one to a line (JSON Lines), because the
predicate type holds one audit per Statement. A file that is not UTF-8,
or that changes while it is being attested, is an error (exit 2), not a
Statement. Without --output or --json it prints a summary: each
file's digest, whether it compiles, its effects, and ffi_any when
set. velaris.attest(path) in the library returns the Statements as a
list; STABILITY.md adds it to the stable API.
When the audit cannot tell, the Statement says so. Two fields are
added to velaris.audit/1, optional within version 1 (velaris-spec
8.2), so that an attestation can state what was not determined instead
of leaving it out:
counts: forfsandnet, the most operations one call to a function the file defines can perform, by velaris-spec 9.4's rules - the same code (_operation_bounds) the capability baseline uses.0for an effect none of them declares;nullwhere the text fixes no bound, such as a loop overargs(); andnullas a whole when the file does not compile.prover: whether a prover decided the promises' status. It is false without one, where no status isprovenand aproven_shareof 0 says nothing about what could be proven; and false when the file does not compile.
With the fields already there - ok false and its problems, ffi_any
for a module named while running rather than a shorter module list,
read_any, write_any and any for a path or URL built while
running - the Statement carries whatever the audit could not determine
as the audit states it. EMBEDDING.md's table of audit fields has both.
load_program takes an optional list it appends each file it reads
to, which is how attest names the imports it digests.
Signing is left to Sigstore's tools. Nothing here signs.
EMBEDDING.md gives working commands for cosign 3 (cosign attest-blob --statement, keyless and with a key pair, and cosign verify-blob-attestation, which fails when the file is not the
Statement's subject by digest) and for sigstore-python 4, whose command
line attests only SLSA provenance, so the library's sign_dsse and
verify_dsse are used. The cosign key-pair commands were run here
against a Statement from this build with cosign v3.0.6, offline - a
signing config naming no transparency log, and --insecure-ignore-tlog
to verify: it signed, verified, and refused an edited effects.vel
("provided artifact digest does not match any digest in statement").
The sigstore-python snippet was run as far as the browser sign-in,
which needs a person.
The release signs one. release.yml has a new job, attestation:
it installs this commit with the prover, writes velaris attest examples/effects.vel with SOURCE_DATE_EPOCH at the commit's time,
signs the Statement keylessly as a DSSE envelope with cosign and with
sigstore-python, verifies both bundles against the workflow's own
identity - cosign against the example's bytes, sigstore-python by
comparing the signed Statement and the first subject's digest - and
attaches velaris-attestation-X.Y.Z.intoto.json and the two
.sigstore.json bundles to the release. The workflow can also be run
by hand (workflow_dispatch); then only this job runs, signing and
verifying under the branch's identity and publishing nothing, and the
jobs that build and publish packages run only for a tag. SECURITY.md
gives the command to verify the attestation.
Tests. check_library.py gains 12 checks on five programs in a
scratch directory and a two-file program: a directory gives one
Statement per .vel file; every digest is the sha256 of the file's
bytes; each predicate's audit equals audit() of the same program,
field for field; every Statement validates against in-toto's Statement
v1 schema, the capability/v1 predicate schema this repository
publishes, and velaris-spec's velaris.audit/1 schema; a module named
while running is ffi_any, with an empty module list; a count the text
fixes is its number and one it does not is null; a program that does
not compile is ok false with its problem, counts null and prover
false; prover matches whether a prover is installed; an imported file
is the next subject, with its own digest; and the command line's
--json is the library's Statement, its time fixed by
SOURCE_DATE_EPOCH. in-toto publishes its Statement schema as prose and
protobuf, not JSON Schema, so tests/in-toto-statement-v1.schema.json
is written from its statement.md, resource_descriptor.md and
digest_set.md at commit 2dcd055e, which the file names. CI checks
out velaris-spec before the suites run, so check_library.py holds the
audit to the spec's schema on every leg.
velaris-spec 0.5 records the producer in section 8.5 and the two
fields in 8.2 and its audit schema; its examples/capability-statement.json
is now what velaris attest examples/effects.vel writes at this tag,
and REGISTRY_SUBMISSION.md names the producer, the command that fetches
the signed Statement, and embeds that Statement. Still not submitted.
The author's name. The author is Palakurthi Gowri shankar: family
name Palakurthi, given name Gowri shankar. CITATION.cff in both
repositories now says family-names: Palakurthi and given-names: Gowri shankar, and both validate against the CFF 1.2.0 schema (with
jsonschema and with cffconvert --validate). The full name replaces
the earlier forms in LICENSE, the VS Code extension's LICENSE,
README.md, SUPPORT.md, MAINTAINERS.md, PROVENANCE.md, pyproject.toml,
the npm, MCP bundle and winget manifests, the LangChain integration's
metadata, paper/velaris.md and paper/references.bib (as
{Palakurthi, Gowri shankar}, the comma form: without the comma BibTeX
would read shankar as a particle like "van"), and in velaris-spec's
NOTICE, README.md and REGISTRY_SUBMISSION.md. In a reference list the
family name leads - "Palakurthi, Gowri shankar" in full; abbreviated,
BibTeX writes "Palakurthi, G. s." and cffconvert's APA-like form
"Palakurthi G.s." - and a citation in the text is "(Palakurthi, 2026)".
A CSL processor (citeproc-js) abbreviates only a capitalised given
name, so its APA style writes "Palakurthi, G. shankar".
Provenance. Both PROVENANCE.md files record the second pair of Software Heritage saves, 2471049 and 2471050, taken after the 4.1.0 and 0.4 push, with their snapshots, beside the first pair.
Sources, named (CONTRIBUTING.md rule): the parts of this release
were specified by the maintainer. The Statement follows in-toto's
Statement v1 and ResourceDescriptor specifications, and its directory
form the JSON Lines layout of in-toto's Bundle specification, at commit
2dcd055e; the signing commands follow cosign's and sigstore-python's
own documentation and were checked against cosign v3.0.6 and
sigstore-python 4.5.0.
Verified, on Windows 11 with Python 3.13, with the proof cache
cleared first. With the prover (z3 5.1.0, llvmlite 0.49.0):
run_tests.py 92/92, check_library.py 196 correct (one skipped: its
symbolic-link case is POSIX only), check_sandbox.py 49 (its
symbolic-link case skipped: this machine will not make a link),
check_fallible.py 26, check_refusals.py 21, check_termination.py
44, check_pool.py 39, check_ratchet.py 114, none wrong;
fuzz_native.py 30 agrees; benchmark/run.py --quick --check matches
results.json; velaris test examples/std_test.vel 7/7, velaris examples/edges.vel 20/20, velaris fmt --check clean, velaris capabilities check . passes; velaris conformance reports L1, L2 and
L3 conformant, 443 of the 444 cases run and the symbolic-link case
skipped, and --level 1, --level 2 and --level 3 each report their
level conformant; build_conformance.py --check matches velaris-spec's
corpus. Without the prover, in a fresh virtual environment holding this
tree and jsonschema and no z3 or llvmlite, all of the same pass, with
check_library.py 193 correct (two skipped for needing the prover, one
POSIX only) and check_refusals.py 11 with 10 skipped for needing the
prover. With the prover and without it, velaris attest examples/effects.vel at a fixed SOURCE_DATE_EPOCH writes the same
Statement except prover. The audits of the 107 .vel files directly
in examples/ and stdlib/ (73 compile, with counts set and prover
true; 34 do not, with counts null and prover false) validate against velaris-spec 0.5's audit schema through its
tools/validate.py --audits, as does this repository's
velaris.capabilities; its tools/validate.py (schemas, examples, the
example Statement, the 444 cases) and tools/check_sync.py pass. Both
CITATION.cff files validate against the CFF 1.2.0 schema. The
release workflow's attestation job has not run before this commit's
push, so whether it signs and verifies in GitHub's runners is for that
run to say.
4.1 - Conformance you can run, provenance you can check #
A minor version. Conformance to the capability format stops being a
sentence naming this repository's suites and becomes a corpus any
implementation can run; velaris conformance runs it here, on every CI
leg. The project gets citation files, an entry in an independent
archive, and a predicate type whose URL resolves; and a preprint is
drafted, for the maintainer to read. One defect, found by writing the
corpus, is fixed.
4.0.1 was committed and pushed but never tagged, so it was never
published; its fix to velaris review ships in 4.1.0.
The conformance corpus. velaris-spec 0.4 holds tests/: 444 JSON
cases, each with an id, its level, a description, the input - budget
text, Velaris source, a tree of files and a change to it - and the
outcome an implementation must produce: the grants a budget parses to
or its refusal, an audit's effect surface, a refusal and its code, a
baseline, or a verdict with every widening and the rules it fails. 298
cases at L1, declaration (280 budgets, 18 audits); 37 at L2,
enforcement (programs run under a budget, with a fixture of files and
two local HTTP servers); 109 at L3, the ratchet (14 baselines to write,
51 changes to check, two sequences, the writer's guard, and 32
covering, 3 reduction and 6 operation-bound cases). velaris-spec's
CONFORMANCE.md defines the levels and the behaviours each requires, and
says plainly that no level requires a prover; tests/README.md is the
runner contract. Five L3 cases are the ratchet's known limits from the
4.0 entry below, recorded with the outcome the check gives today - two
widenings that pass, three non-widenings that fail - so that another
implementation matches it rather than guessing.
The corpus is not written by hand. build_conformance.py transcribes
it from tables in three suites, where the same entries are asserted
against this implementation, and computes nothing itself; --check
regenerates it and fails when it differs from what velaris-spec
commits. For that, the suites became tables, and asserting them made
them stricter:
check_sandbox.pyholds each case as data, with placeholders for its paths and ports, and asserts the refusal code each must carry. Until 4.1 it accepted any of the five codes, so a runtime refusing a path outside its prefix with E310 rather than E313 would have passed. It runs its fixture in a temporary directory rather than in the repository, and its symbolic-link case wherever the system will make a link, not only on POSIX. Four cases are new, the four rules velaris-spec 0.3 listed as untested (its Q9):@0, a count spent by an operation that then fails, a URL with no scheme taken as HTTPS, and an existence check under a write-only grant. 34 escape attempts and 16 honest programs.check_library.pygainsBUDGETS, 55 budgets each held to what velaris-spec sections 4 and 5 say it means - the grants for 51, a refusal for 4 - including the spec's own examples and denials (until 4.1 the awkward ones were checked to round-trip, not for what they parse to); andAUDITS, 18 programs each held to the effect surface its audit must report, and to the schema. The malformed-budget list moved out ofmain()so the corpus can be written from it.check_ratchet.py's scenarios becameDERIVE,CHECKS,SEQUENCES,WRITE_GUARD,COVERING,REDUCEandBOUNDS. Each check now asserts the complete list of widenings, with the rules each fails, where most cases asserted one finding; each derivation asserts the whole baseline. New: 14 baselines written for trees; the writer refusing, unasked, to write a baseline for a tree that needs more; a new effect; a new program outside the surface (W1 alone);fsdeclared with no file named, which needs plainfs; another spelling of a path; a port under a portless grant; 9 covering and 2 reduction cases; and the five known limits. What only this implementation says, such as the call chain,velaris review, SARIF and warning text, is asserted after, on the same scenarios.
Thirteen scenarios are left out of the corpus, and tests/index.json
says why each: six attempts to reach an ungranted Python module through
a granted one, which depend on Python's object model; four honest
programs that need a Python host; a run given no budget, which the
format leaves to the implementation; and two about this command line's
flags.
velaris conformance [--level 1|2|3] [--json] [--corpus DIR] runs
the corpus against this implementation, through the doors another
implementation would use: the budget parser, the audit, the command
line under a budget, the baseline writer and the check. It finds the
corpus beside the working directory or the installation, prints a line
per level and a one-line verdict, and exits 1 if any case of a level
asked for fails. --json is velaris.conformance/1, one result per
case, the report shape tests/README.md gives any runner. --level N
runs the cases a claim at level N needs: L1 and N. A case that needs a
symbolic link is skipped where none can be made, and the verdict says
so; without jsonschema the cases that validate a document against
velaris-spec's schemas are skipped too, and the level is reported as
not shown. It passes at all three levels. Run against a copy of the
corpus with twelve expectations broken, covering all ten kinds of case,
and one case given a kind no runner knows, it fails exactly those 13
cases and exits 1.
CI. Every one of the twelve legs checks out velaris-spec, runs the
drift test and velaris conformance against its corpus. velaris-spec's
own CI runs the drift test and this implementation's conformance
against its corpus, beside its schema and sync checks.
Found by the corpus, and fixed. The audit of a program refused for
naming something that is not an effect in a uses clause (uses io, teleport, E300) still listed that name in effects and in the
function's effects, and wrote a safe_command that does not parse.
velaris-spec had said since 0.2 that effects holds only the seven
names and that safe_command always parses; 3.3 had fixed the compile
check and not the document that reports it. From 4.1 audit leaves any
name that is not an effect out of all three; the E300 still names it.
This is not a breaking change under STABILITY.md: the fields' documented
meaning is unchanged, and it is the implementation that now matches it;
what changes is the content of a document whose ok is false, which
velaris-spec says bounds nothing. HALL_OF_FAME.md credits the corpus.
velaris-spec 0.4 corrects the sentences that had inferred from 3.3's
rejection more than the implementation did.
Provenance. CITATION.cff in both repositories (CFF 1.2.0, checked
against the format's schema), with the author, the repository, the
version and its date, and a note that a preprint is forthcoming; both
READMEs say "Cite this repository". Both repositories were submitted to
Software Heritage through its save-code-now API, and PROVENANCE.md in
each records the save requests' ids and dates beside the first commit
of the effect system, the date velaris-spec 0.1 was tagged, and that
velaris-lang is the reference implementation.
A resolvable predicate type. velaris-spec 0.4 section 8.5 defines
an in-toto predicate type for velaris.audit/1 bound to the digests of
the files audited, and its URL is on this repository's documentation
site: https://gowrishankar-infra.github.io/velaris-lang/capability/v1,
which build_docs.py now writes, with schema.json beside it,
identical to velaris-spec's schemas/capability-predicate.v1.schema.json
(velaris-spec's tools/check_sync.py fails if they differ).
velaris.dev was not used: on 2026-09-11 it answered every path with a
Vercel DEPLOYMENT_NOT_FOUND, and nothing here says who controls it.
This implementation publishes the type and does not yet write
Statements of it; velaris-spec's example was assembled from velaris audit --json and a file's digest. A pull request listing the type in
in-toto's predicate registry is prepared in velaris-spec's
REGISTRY_SUBMISSION.md, and not sent.
A preprint, drafted. paper/velaris.md and paper/references.bib:
the problem, the design, the implementation, the evaluation, related
work, limitations and a reproducibility section, with a table naming
the file each number comes from. Not submitted anywhere.
Also corrected. The documentation site's benchmark table said
"Velaris 3.1" and the README's did too, where benchmark/RESULTS.md
said the table was produced by 3.0.0. The table was regenerated with
4.1.0: every verdict and every line of evidence is what 3.0.0 produced,
and only the version line of RESULTS.md and results.json changed;
the pages now say 4.1. EMBEDDING.md's table of audit fields lacked
ffi_any, added in 4.0. velaris-spec's PRIOR_ART.md said this
implementation emits no SARIF, untrue since 3.4.
Sources, named (CONTRIBUTING.md rule): the parts of this release
were specified by the maintainer. The runner contract follows no
published harness. in-toto's docs/new_predicate_guidelines.md and
predicate template, read on 2026-09-11, shaped REGISTRY_SUBMISSION.md.
Hills, Caspary and Cooper Stickland, "Distributed Attacks in
Persistent-State AI Control" (arXiv:2607.02514), is cited by the paper
and by velaris-spec's PRIOR_ART.md as the gradual-attack result the
ratchet addresses; the record does not say it influenced the ratchet's
design in 4.0, and this entry does not claim it did.
Verified, on Windows 11 with Python 3.13, with the proof cache
cleared first. With the prover: run_tests.py 92/92,
check_library.py 184 correct (one skipped: its symbolic-link case is
POSIX only), check_sandbox.py 49 (its symbolic-link case skipped: this
machine will not make a link), check_fallible.py 26,
check_refusals.py 21, check_termination.py 44, check_pool.py 39,
check_ratchet.py 114, none wrong; fuzz_native.py 30 agrees;
benchmark/run.py --check over the whole table and --quick --check
match results.json; velaris test examples/std_test.vel 7/7,
velaris examples/edges.vel 20/20, velaris fmt --check clean,
velaris capabilities check . passes; velaris conformance reports
L1, L2 and L3 conformant, 443 of the 444 cases run and the
symbolic-link case skipped; build_conformance.py --check matches
velaris-spec's corpus. Without the prover, in a fresh virtual
environment with jsonschema and no z3 or llvmlite, the same pass - the
benchmark with --quick --check only - with check_library.py 181
correct (two skipped for needing the prover, one POSIX only),
check_refusals.py 11 with 10 skipped for needing the prover, and
velaris conformance again conformant at L1, L2 and L3. velaris-spec
0.4's tools/validate.py (schemas, examples, the example Statement,
all 444 cases against the case schema) and tools/check_sync.py (the
quoted sections, and the predicate schema against
docs/capability/v1/schema.json) pass against this tree. The symbolic-link cases run where a link can be made - the
Linux and macOS runners, and the Windows runners if they allow it -
and whether they pass there is for this commit's CI to say.
4.0.1 - The review read the old files from the wrong place #
4.0.0's CI failed on its four Windows legs, in check_ratchet.py: the
five checks that go through velaris review failed. The capability
check - the gate - passed every one of its cases on all twelve legs.
The cause was in review. To find where the checked directory sits in
the repository, it compared the working directory's path with the path
git rev-parse --show-toplevel prints, as text. On the Windows runners
the temporary directory is a short name (C:\Users\RUNNER~1\...) and
git prints the long one, so the two did not match, and the files at the
ref were materialised and read from a directory that was not the ref's
place in the tree. The review then reported the surface as widened, or
as unchanged, whatever the change was. review now asks git where it
is (git rev-parse --show-prefix) and compares no paths. The same
mismatch happens wherever the path to a checkout goes through a link or
junction, on any system.
The 4.0.0 entry's "Verified" paragraph was true of the machine it
names, from a path git spells the same way, and was not true of the
Windows runners. check_ratchet.py gains a case that runs review
from a junction (Windows) or a symbolic link (elsewhere) to a
repository whose working tree needs more than its last commit, and
requires the review to see it: the case fails against 4.0.0 and passes
now. 63 checks.
Affected: velaris review, and the review section of the Action's
pull-request comment, on a machine where the two paths differ.
velaris capabilities init and check were not affected, and neither
was anything else in 4.0.0. This repository's velaris.capabilities is
recorded again under 4.0.1; only its velaris_version changed.
Verified, on Windows 11 with Python 3.13, with the proof cache
cleared first; the new case fails against 4.0.0's velaris.py and
passes against this one. With the prover: run_tests.py 92/92,
check_library.py 165 correct (one skipped, POSIX only),
check_sandbox.py 45, check_fallible.py 26, check_refusals.py 21,
check_termination.py 44, check_pool.py 39, check_ratchet.py 63,
none wrong; fuzz_native.py 30 agrees, benchmark --quick --check
matches, velaris test examples/std_test.vel 7/7, velaris fmt --check clean, velaris capabilities check . passes. Without the
prover, in a fresh virtual environment with no z3 or llvmlite: the same
thirteen pass, check_library.py 162 correct (two skipped for the
prover, one POSIX only) and check_refusals.py 11 with 10 skipped.
Whether the Windows runners agree is for this commit's CI to say.
4.0 - The operator sets the limits, and the capability surface cannot widen quietly #
This is a major version. Two gaps 3.4 left open on the doors are closed, which changes what a door started without flags will grant; the project's stability policy is written down for the first time, with the record of the times it was broken; and a repository can now declare the capability surface its programs may have and have CI fail any change that widens it.
The operator sets the limits, not the caller. On the HTTP door and
the MCP server a request's timeout and max_memory_mb were whatever
the caller sent; the 30 seconds and 512 MB the doors have had since
2.59 were only the values used when a caller sent none. Before 4.0 a
caller could exceed them, by asking. Both doors now take
--max-timeout and --max-memory-mb, 30 seconds and 512 MB when the
flags are absent. A request that names neither gets the ceiling; one
that asks for less gets less; one that asks for more is refused the way
an over-wide budget is - 403 from the HTTP door, isError from the MCP
server - with the ceilings named in the body (max_timeout,
max_memory_mb, beside max_allow), and logged with outcome
ceiling. A value that is not a number above zero - text, true,
zero, a negative, a fraction of a MB - is a bad request. A flag value
that is not a limit stops the door at start. GET /health with the
token reports all three ceilings. The rule lives in one place,
velaris.run_limits, used by both doors.
The HTTP door's default ceiling is io. Without --max-allow,
velaris serve granted every effect, ffi included, to anyone holding
the token; the MCP server has defaulted to io since 3.4. The door now
does too, and starting it wider takes naming the grants:
--max-allow io,env,fs,net,clock,rand,ffi is what 3.4 granted by
default. Its start-up lines say which ceiling is the default.
One more change came with the flag: velaris serve --max-memory-mb
was accepted before, and - through the command line's process-wide
--max-memory-mb - set an address-space cap on the door's own process
on Linux and macOS, never documented. It is now the most each run may
have, and the door's process is not capped.
STABILITY.md. What semantic versioning covers here - the language
as SPEC.md states it, the error codes, velaris.audit/1, the library
API, the budget grammar, the command line's commands and documented
flags - and what it does not: internals, the proof cache, the wording
of messages, which promises happen to prove, the formatter's style,
anything marked provisional. The rules: breaking changes only in a
major version, security fixes included; a deprecation announced in a
minor version, warning for at least one more, removed no sooner than
the next major; an error code never reused for another meaning, and a
removed one kept listed as removed (REMOVED_ERRORS, and a section on
the errors page - empty today). A stronger prover refusing a program
its proof shows wrong is stated as the one exception, and why.
Its "Breaks we have made" section is longer than expected when it was
begun. 2.0 and 3.0 broke in major versions, as promised. 3.4 shipped
three breaking changes in a minor version and named them as such, and
it should have been 4.0; it was not retagged because 3.4.0 was
already published, and this policy exists so that it does not happen
again. Reading every entry for STABILITY.md found more: 3.4 broke three
further things it did not list (serve and the MCP server refusing an
unknown argument, GET /health without the token no longer naming the
ceiling); 3.3 shipped five breaking changes as fixes and named none;
and eight earlier minor releases, 2.20 to 3.1, each broke something -
64-bit integers, the formatter's style, three builtins made fallible,
the http module's call, audit().problems, the doors' 2.59 limits,
args(), Windows memory caps. E610 was reused, in 2.59, for a meaning
other than the one 1.4 gave it. All of it is listed there, and 2.0 is
recorded as its entry and commit describe it: four builtins made
fallible. README and CONTRIBUTING link it; SPEC.md section 15 points
to it.
Also corrected: the 3.3 entry of this file lost its heading in the 3.4 release, so its text read as part of 3.4. The heading is restored.
The capability ratchet.
velaris capabilities init [path] # write velaris.capabilities
velaris capabilities check [path] # exit 1 if the surface widened
velaris review --against <ref> [path] # a pull request's delta, as factsA change can add capability to a repository a little at a time - a helper that builds a URL, a function that reads a file, a call three levels down that sends one to the other - and no single diff looks alarming, which is how such a change passes a reviewer and a diff-based monitor. Capability is binary and cumulative, so the steps add up to exactly what one large step would have done; but only a comparison with a declared baseline sees the sum. This release makes that comparison, and makes it the only one the gate uses.
velaris capabilities initreads every.velfile under the path, except in.gitand what git ignores, and writesvelaris.capabilities(velaris.capabilities/1, velaris-spec 0.3 section 9): the repository's surface - every grant its programs need, in the budget grammar, and forfsandnetthe most operations one run can perform, ornullwhere the text sets no bound - and for each program its own grants and counts and the effects each of its functions declares, or that it does not compile; with the Velaris version and the date. One grant per line and one function per line, so accepting a widening is a one-line diff. It refuses to replace an existing file without--force.velaris capabilities checkderives the same from the working tree and compares it with the file - never with the previous commit - and exits 1 when anything widened: a grant the surface does not cover (a new effect, a new module, a path outside every recorded one, so./datawidened to./fails; a host, soapi.example.commade*.example.comfails; a scoped grant made unscoped); morefsornetoperations in a run than recorded (10 raised to 1000 fails); a program the file records needing something its own entry does not give, even when another program already had it; and a function the file records gaining an effect, even when its program's grants and counts stay the same. Narrowing never fails and is reported. A new program is held to the surface alone. Each widening names what widened, the file, function, line and call that introduced it, the chain of calls frommainthat reaches it, and the edit to the file that would accept it. A baseline from another Velaris version is compared with a warning, not a failure. A baseline that is missing, is not/1, or does not read exits 2 and never passes.--jsonisvelaris.capabilities-check/1;--sarifreports each widening as an error at its line, through the 3.4 SARIF code, with two new error rules,capability-widenedandcapability-effect-gained, and a note,capability-narrowed, on the errors page.- What a program needs is read from its text: the effect and type
checks run and the prover does not, so the result is the same with
and without z3. A path, URL or module is taken as named when it is a
literal or a variable bound once to one; one built while running is
recorded as the unscoped grant, which a scoped baseline does not
cover. The operation bound comes from loops whose counter and limit
the text fixes -
for i in 0 to 10, a counter started bylet, a list literal - multiplied through nesting and calls; a loop the text does not bound, and recursion, have none. velaris review --against <ref>runs the derivation and the audit on the files at a git ref - read withgit show <ref>:<path>, nothing checked out - and on the working tree, and reports whether the capability surface changed, the proven share before and after, functions that became fallible, hosts, paths and modules newly named, whethervelaris.capabilitiesitself changed, and one word of risk computed from those facts alone:highwhen the surface widened, or the declared surface widened or was removed;mediumwhen it did not but a program or function the ref had came to need more, or the proven share fell;lowwhen nothing widened and the proven share did not fall. No count of changed lines enters it. It informs a reviewer; the gate ischeck, since a review against the commit before loses a widening the moment it is merged.- The GitHub Action has a
capabilitiesinput,checkby default whenvelaris.capabilitiesexists andoffotherwise - except that a pull request deleting the file fails, since that would turn the ratchet off;capabilities: offin the workflow is the visible way to do that. Its findings are uploaded to code scanning beside the check's whensarifis on. The pull-request comment from 2.63 now holds the ratchet's result, each widening with the edit to the baseline that would accept it, and the review delta against the pull request's base. - This repository commits its own
velaris.capabilities, written byvelaris capabilities init .: 172 programs, 22 of them examples and benchmark rows built to be refused, recorded as not compiling. CI runsvelaris capabilities check .on every leg. velaris.audit/1gainsffi_any, added within version 1: true when some Python call names its module with a value built while running, whichffi_modulescannot list. The ratchet needs it - a computed module name must not slip past a baseline namingffi:math- and it closes velaris-spec's open question Q4. The audit also warns when it is true.
check_ratchet.py, 62 checks, proves the rules rather than
asserting them, through the command line in scratch trees and real git
histories. The gradual case: six commits, the first five adding pure
helpers, a text constant holding a URL and a call to print, each
passing against the baseline, and the sixth sending a summary to that
URL through a helper three calls below main - which fails, naming
net:collector.example.net as a new effect, lib/deliver.vel line 2
in send, and the chain main -> summary -> deliver -> send, while a
review of each commit against the one before calls the first five
low. Why the gate must be the baseline: a count widened and merged
anyway keeps failing the check at every later commit, while a review
against the previous commit reports nothing one commit later; and seven
steps from 10 to 1000 are reported as 1000 against the declared 10,
where the previous commit shows 640 to 1000. Widenings through an
import (the surface unchanged, the program's entry not), through the
standard library, through a path prefix, a count, a wildcard host, a
URL, path and module built while running, a new module, a new
direction, a hidden directory, and a program whose main is imported
from outside the tree. An effect added to a function whose program's
grants and counts stay exactly as they were, reported. And the changes
that must pass: narrowing; reordering functions, imports, uses
clauses and statements, renaming locals, reformatting and velaris fmt; a file with no effects; a new program inside the surface; a
literal moved into a variable; a function renamed or moved to another
file; a program that stops compiling. Declared prefixes, wildcards and
ports; a baseline from an older and from a newer version (a warning
that never hides a widening); init without --force; four baselines
that cannot be read (exit 2); the JSON and the SARIF, which validates;
23 covering cases and 6 operation-bound cases. CI runs it on every leg.
What the ratchet cannot do, and the cases where one of the two rules
was not achieved. A widening never passes and a non-widening change
never fails, among the changes check_ratchet.py holds; beyond them,
these are the known limits, each stated in THREAT_MODEL.md:
- A widening that passes: a function renamed in the same change that
gives it an effect is a new function, held to its program's entry and
the surface but not to what its old name declared - so a pure helper
renamed while it gains an effect its program already had is not
reported as a function that gained one. What a granted
ffimodule does is outside the text. A symbolic link under a recorded directory is that directory's content. - A change that does not widen, reported as one: the bound on
operations comes from fixed rules, so a loop bound moved behind a
function call (
for i in 0 to limit()) loses its bound and is reported as unbounded; a path or URL passed to a helper as a parameter is taken as unscoped, as it always is, even when every caller passes a literal; and a path written with\is covered only by the same text.
What a 3.4 user has to change.
- A client of
velaris servethat relied on the door granting more thaniowithout--max-allowmust start the door with--max-allownaming what it needs. - A client of either door that sends a
timeoutover 30 seconds or amax_memory_mbover 512 must have the operator raise--max-timeoutor--max-memory-mb, or ask for less. - A client that sends
timeoutormax_memory_mbas text ("30"), or0to mean the default, must send a number, or leave the field out. - An operator who passed
--max-memory-mbtovelaris serveto cap the door's own process gets a per-run ceiling instead; cap the door with the operating system (ulimit -v, a container limit) if that was the intent.
Additions, which break nothing: velaris capabilities, velaris review, the Action's capabilities input (off unless the file
exists), ffi_any, and the new SARIF rules.
velaris-spec 0.3. Section 9 stops being provisional:
velaris.capabilities/1 replaces the provisional /0, which no
version of this compiler wrote. The spec states the document, the
derivation from a program's text, the operation bound, the covering
rule (a path holding \ now compared whole), and the comparison as
five rules, W1 to W5, with a table of what widens for each kind of
scope - effect, path, host, module, count, function - and the rule that
a check compares with the baseline and with nothing else. Q4 is
resolved by ffi_any. Its conformance section adds check_ratchet.py.
It still quotes this repository's SPEC.md sections 6, 7 and 7.1 word
for word - none of them changed - so its drift check passes; its CI now
also validates this repository's velaris.capabilities against the
/1 schema.
Sources, named (CONTRIBUTING.md rule): the four parts of this
release were specified by the maintainer. No published work is on
record as the source of the ratchet's design, so none is named. The
CHANGELOG scan behind STABILITY.md's record was made in this release,
and its findings were checked against the entries and against git
history (E610's two meanings are in commits 616579f, 79808fc and
b5a4582).
The MCP manifest changes, by design. velaris_run's description
and the descriptions of its timeout and max_memory_mb inputs now
name the ceilings, so the signed velaris-mcp-tools-4.0.0.json differs
from 3.4.0's: velaris mcp-verify against a 3.4.0 manifest reports
velaris_run as CHANGED, which is what it is for.
Verified, on Windows 11 with Python 3.13, before tagging, with the
proof cache cleared first. With the prover: run_tests.py 92/92,
check_library.py 165 correct (one skipped: the symlink case is POSIX
only), check_sandbox.py 45, check_fallible.py 26,
check_refusals.py 21, check_termination.py 44, check_pool.py 39,
check_ratchet.py 62, none wrong; fuzz_native.py 30 agrees,
benchmark/run.py --quick --check matches results.json, velaris test examples/std_test.vel 7/7, velaris fmt --check clean, and
velaris capabilities check . passes on the clean tree. Without the
prover, in a fresh virtual environment holding this tree, jsonschema
and no z3 or llvmlite: the same thirteen pass, with check_library.py
162 correct (two skipped for needing the prover, one POSIX only) and
check_refusals.py 11 with 10 skipped for needing the prover. Every
workflow file and action.yml parse as YAML. The Action's new steps
were run outside GitHub as far as they go: the ratchet step under Git
Bash in four cases (no baseline, off; a baseline and nothing widened,
pass; clock added, fail naming it; a pull request deleting the
baseline, fail), and the comment's Python against real capabilities check --json and review --json output. velaris review --against HEAD over this repository took 45 s with the prover. velaris-spec
0.3's tools/check_sync.py passes against this tree, its
tools/validate.py passes with --capabilities on this repository's
velaris.capabilities, and the velaris.audit/1 documents 4.0.0
produces for all 109 files in examples/ and stdlib/ validate
against its audit schema, ffi_any included.