CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
All notable changes to revl are recorded here. Versions follow the IR contract:
a major version is an IR-breaking cut, a minor version adds without breaking.
## 0.3.0rc1 (unreleased) - the 0.3.0 release candidate
0.3.0rc1 is the release candidate for revl 0.3.0. It is a beta: unreleased, and
open to change before 0.3.0. It is the minimal clean, self-consistent six-tier
cut plus the live-systems demo: self-host front end, cross-tier execution,
six-backend conformance, and per-tool-call agent effects on all six tiers. It
also folds in the full agent story and the standing correctness debt.
### The language
- Self-hosted front end. `selfhost/lexer.rvl` and `selfhost/parser.rvl` compile
at ir_version 3 and are cross-checked against the reference implementations as
a differential oracle: two independent implementations of one grammar forced
to agree, so a disagreement is a real defect in one of them.
- Six-backend conformance (py, ts, rust, go, java, wasm). The autogenerated
conformance matrix is published and green; the compiler emits itself with no
divergence on py, ts, and java.
- Typing frontier narrowed: match-arm checking in check-position, generics
instantiation, `type X = Y` alias resolution matching TypeScript, and six
accept-but-won't-compile holes each verified against `javac` and `cargo`
and closed. This is not a claim that none remain; the open ones are listed
under Known issues.
- **Declared resource dimensions (issue #1938, fixed by #2149).** A composition can now declare its
own capability's resource parameters:
`capability mail.send(account: discrete, folder: path)`. The parameter is
bound from the call arguments and then appears in the approval ticket's
spelling and in the audit token, so `revl audit` records which mailbox is
sending instead of the fact living only in a gate's own spelling. The kind
vocabulary stays closed (`path`/`discrete`/`ceiling`, the three orders the
core vocabulary already used) and the registry stays closed against
UNDECLARED names, so a typo is still refused at parse. A composition that
declares no dimension is byte-identical everywhere; `INTERCHANGE_VERSION`
stays `1.0` (the declaration is an IR member, not an interchange member).
See docs/design/294-parameterized-capabilities.md.
- `try e`: Result propagation in a fn body (#1967). Expression-bodied top-level
fns, template scope in block-bodied match arms, and `Float.to_str` let eight
realistic components compile on the first draft (#633, #570, #703).
- The `Stream[T]` reactive surface across the tiers: `every ... in` iteration,
`on ... as` typed-event handlers, the map/filter/take combinator chain,
backpressure policies and the block drain window, provider-declared replay
with its last-n backlog, and the required `Stream[T]` coeffect. A tier that
cannot lower a form refuses it by name (#507, #636, #679, #747, #792, #857,
#982, #1042, #1060, #1066, #1076, #1105, #1179, #1523).
- Endpoints: a `route` clause with derived artifacts, an opaque `Principal` with
an admission refusal for forged authority, and the `host` row bound to the
Cordis server (#775, #782, #787). The stdlib gains typed HTTP contracts
(`Request`/`Response`, `Handled`/`NotHandled`), the canonical request-framing
primitive, HS256 JWT verification, and context-scoped templates with per-hole
escaping (#745, #881, #1556, #861, #793).
- Frontend assets: typed, jailed, content-pinned asset handles, the
insertion-site source map chained into the bundler's, run-time reads of pinned
assets, and a reactive data channel declared in revl and projected to the
frontend (#1043, #1057, #1064, #1092, #976). `revl dev` runs the scoped WebUI
host and the Vite frontend as one command (#833, #984).
- Persistence: a driver-independent typed-model store with store-owned key
assignment, a memory driver and a sqlite driver (#767, #778, #806). `revl
gen-types` turns a typed model document into revl types and checks its digest
at compile (#1958).
- A predictable service lifecycle contract (startup, readiness, cancellation,
disposal, hot replacement), with native conformance coverage (#760).
- `extern local`: a durable write recorded without being a crossing (#2177).
- Declared intent bounds an operation, not its emit steps. The
intent-versus-action refinement kernel, the explicitly-related object and
set-valued scope, the stating slot for a value-returning crossing, and the
comparison of a standing grant as a declaration at the class-(c) gate (#855,
#971, #1007, #1031, #1058, #1061, #1073).
- Retention: the `Retained[T, <policy>]` qualifier, a signed erasure receipt
(`revl retention-receipt`), and a refusal of a past-deadline value that a
provider body persists (#988, #1018, #1177, #1002).
- Cross-tier divergences closed in `Int.MIN` arithmetic, Float division and
remainder, Str ordering and character access, `${...}` rendering, list bounds,
closures, and numeric parsing, plus short-circuit `&&`/`||` on wasm (#1086,
#1118, #1082, #1106, #1094, #1088, #1138, #1039). Wasm refuses `Int / Int` by
name instead of lowering it to a signed divide (#2235).
- Refusals added: an undeclared nominal type is `T-UNRESOLVED` rather than a
mismatch (#863, #902); a statically out-of-range list index (#951); a
non-finite Float in `json_stringify` (#945); an ADT case that collides with
another module's name (#1146); an `extern fn` and a `fn` of one name (#1880);
two component names that differ only in case or underscores (#2272); a method
call on a record in a component body (#1687); a capability token that cannot
match, which is now a parse error so a deny rule cannot go silently inert
(#1992); a null-ambiguous `Opt` at the JSON boundary (#1270). `pub` names are
module-scoped (#2005) and local bindings shadow outer declarations in the
resolver (#1990).
- `revl grammar` exports a source grammar derived from the parser, `revl_grammar
{prompt: true}` serves the complete prompt grammar, and `revl idiom` serves
the minimal correct example of each construct (#1663, #2175, #1761, #2283,
#2298). `revl test` gains `--list`, `--filter` and `-v`/`--report` (#866,
#882). `revl explain` covers every diagnostic code with a `fix` and a
`guarantee` (#2048). JSON diagnostics name the declaring file relative to the
invocation and `--json-diagnostics` is registered on all six verbs that read
it (#2072, #2226, #2030).
- The tree-sitter grammar covers syntax 2.0 and its conformance gate is green
(#639).
- Self-host: the self-hosted emitters caught up with the reference and the last
three tier emitters are wired into the native compile driver (#1032).
`emit_go.rvl` has zero go divergences, `emit_py.rvl` zero py refusals and
divergences, `emit_rust.rvl` is driven natively and held to the reference's
bytes, and the front end gained the type layer (type-spelling algebra,
expression types, provide-method and component-body types), and the self-host
admission gate enforces the fn-body binding discipline (#1624, #1645, #1143,
#534, #1154, #1160, #981). The native IR producer carries tests, fault tests
and an extern's host refs (#2182, #2162, #2145).
- The formal stack landed together (G4 deferred and approval, G5, G6, G1, A1,
declaration rules), with differential-oracle rows for G9, G-RETAIN,
G-COUNCIL-SPLIT and the witnessed-extern `undo` rule (#1853, #2081, #2082,
#2053, #2122).
- Java: an annotated empty list is declared as its element type, not as a raw
`java.util.List`, so a loop over one (as in `dedent` in `stdlib/str.rvl`)
compiles under javac (issue #2296, fixed by #2302).
- The self-host native driver links a used module's externs. It now builds the
same extern closure as `compile_files`, so a module that uses `stdlib/json.rvl`
keeps its json extern bodies on py and ts (issue #2181, fixed by #2305).
- Go: an `Int32` `+`, `-` or `*` in a component method body traps on overflow,
and so does unary minus, as a top-level `fn` already did (issue #2262, fixed
by #2312).
- Service names that differ only in case, and hot-swap names that fold onto a
running component, are refused on every tier (#2309, closes #2275, #2276).
- A call in a fn body whose callee is a local binding takes its arity and types
from that binding, not from a same-named private fn in another file (#2319,
closes #2311).
- Self-host refusals match the reference. Statement-level parse errors are
refused (#2395, closes #2359), as are the if, match arm, fn and tuple type
forms (#2398, closes #2396) and block arms the reference cannot lift. Test and
prop-test bodies are checked too (#2400, closes #2399).
- Self-host name resolution: a `use` resolves as the reference does, and an
unexported name is refused (#2369, closes #2363). An imported service's record
types are carried into the native link (#2327, closes #2308), and applied
generic records compare by type argument (#2404, closes #2403).
- Self-host reference refusals are named as the reference names them:
UNSUPPORTED on java and wasm (#2388, closes #2383), and on rust and ts (#2377,
closes #2316, #2353, #2367). Reserved ts service keys and namespaced keys are
named on py, rust and java (#2392, closes #2389, #2390), and ts mangles
namespaced host keys as the reference does (#2387, closes #2384).
- Arithmetic in a component method body now executes on py, go, rust and java
(#2376, closes #2198).
- A rowless running component is named on the manifest wire (#2375, closes
#2289, #2334, #2351).
- `str_char` covers every code point through a new `Int.to_char` builtin, so
`str_char(13)` no longer returns an empty string (#2295, closes #2285).
- The ts tier keeps string index scans linear when the loop reads other strings
(#2320, closes #2313).
- The grammar summary names `capability` and `grant` (#2370, closes #2280), and
`Secret[T]` has a secret-value idiom served by `revl idiom` (#2331).
- The ts tier renders a structural record type in a signature, so a block match
arm that captures a record no longer emits an unclosed parameter type, and a
list of a union is parenthesised (`(bigint | undefined)[]`). The self-host ts
emitter matches byte for byte (#2300, closes #2297).
- The java tier boxes `Int32` and spells `Any` in v3 type arguments, so
`List[Int32]` and `List[Any]` compile, and a generic record declares and
applies its type parameters (#2426, closes #2303). Int and Int32 overflow on
java now fail with `revl: Int overflow` and `revl: Int32 overflow`, as
docs/arithmetic.md states for the other hosted tiers (#2425, closes #2381).
- The go tier passes a component `fail` message to `fmt.Errorf` as an argument,
so a `%` in the message is returned verbatim instead of read as a format verb
(#2427, closes #2379).
- A function can state a spec with an `ensures` clause over its parameters and
the returned `result`; it expands to a `prop test`, so the type-derived
generator, the shrinker and the minimal counterexample all apply, and
`revl audit --spec-results` lists each spec with the result of its last
recorded check (#2705, docs/function-specs.md).
### The agent story
- Per-tool-call agent effects (H1) end to end on all six tiers, with residue
enumeration.
- Witnessed-inverse externs: an extern whose checked inverse is another host
extern, so the reversible/irreversible boundary moves from fixed-by-the-world
to moved-by-engineering. The six-tier teardown replays inverses on unload and
abort, and `compensate` adds typed best-effort compensation on the audit
surface, distinct from the proof surface.
- Session commit protocol: deferred emissions enqueue and flush at a two-step
commit, and abort drops the queue and replays the witnessed inverses to prove
a clean world.
- The approval gate (auto-approve unless irreversible): a call's action class is
the worst class over its whole reach. Class (a) revertible auto-approves
silently, class (b) deferrable enumerates at commit, class (c) immediate
prompts per call with an exact-hash single-use ticket. Standing grants let an
operator mint a TTL or uses-scoped approval once; revocation retires it.
- Crash recovery: the WAL is the gate's authority. Recovery replays inverses,
re-prompts a class (c) crossing rather than re-firing it, and refuses to
resume a policy-recorded snapshot into a policy-less session.
- **An agent-authored source that wires a service whose `emission` names no
token is now refused: by `revl_load` and `revl_swap`, not only by
`revl_admit`.** A method that spells `emission` bare declares an effect and
declines to say what it reaches, so nothing in the composition states that it
stops short of the admission kernel, and the kernel-boundary fold can no
longer read it as provably disjoint from the kernel. This is the arm issue
#1265 sequenced behind the composition half (PR #1292) and issue #2105 took:
the refusal asks the declarations, not the token namespace, so a candidate
that composes only pure services is unaffected. The repair on the operator's
side is one word: write `emission[<token>]`, naming the boundary the body
crosses, and every shipped service already does: `stdlib/server.rvl`'s
per-row `Server` included, whose synthesized externs now carry the shim's own
boundary token rather than the row's label. First-party compiles are
unchanged; only the untrusted-author profile the agent verbs ride is
tightened.
- Model placement as a checked route condition: `model role`, `route model`, an
origin ceiling on the role, the role's device profile and candidate set, and
`prefer resident` as a per-arm opt-in to residency-aware ranking (#1220,
#1243, #1423, #2144, #2159). A model council with role-bound members and typed
disagreement gives its refusal its own guarantee code, and its answer type
keeps a disagreement from being ignored (#1257, #1302, #1389, #1392).
- Model decisions leave evidence: a signed `ModelDecision` record with a
fail-closed verifier, sealed at the crossing and replayable from the artifact
(#1239, #1286). A validated emission's response type compiles into a decoding
grammar, and providers apply constrained decoding through runtime adapters for
the common hosted-model wire formats (#1262, #1326, #1524, #1531). Five tiers
refuse a `validated` emission instead of dropping it (#1391, #1413).
- Models are scheduled onto declared devices: the conductor picks one candidate
per routed model action before anything spawns, hands each host's schedule to
its child and enforces it there, provisions each role once, and asks the
servers what a host holds at plan time. When no candidate fits, the placement
is refused (#1468, #1478, #1568, #2106, #2180).
- Promotion from recorded evidence: shadow agreement accumulates over the
decision records and gates a promotion, a landed promotion changes which model
answers, and a promotion is decided from signed execution receipts rather than
a test (#1250, #1297, #1309, #1439, #2104, #2174, #1305, #1363).
- Self-evolution pieces: an evolution controller with one lifecycle and one
verdict, a reward folded from repository counters, held-out scoring on a draw
the candidate could not read, a curriculum derived from repository artifacts,
and a declared evolvable-layer state with a rollback that reports what it
compensated (#1241, #1231, #1258, #1424, #1448, #1230, #1232, #1256). The eval
honesty protocol (EVAL-1) is frozen and checked (#493, #583).
- Computer use: a reversibility classification, UI transaction phases with a
five-way revert split and an operator confirmation gate, taint classes derived
instead of asked, and a transaction unit that settles what it registered when
a call fails on py, ts, go, rust and java (#1242, #1287, #1284, #1320, #1412,
#1969, #1971, #1973, #1977, #1978).
- Quorum approval: multi-party approval with a durable decision graph, operator
verbs for escalate, revoke and override, an admission receipt, and a vote
bound to a credential with a lifetime instead of a name (#865, #974, #994,
#1019, #1035). A policy clause can mark a capability never-standing, enforced
fail-closed (#2010). A distilled auto-approve rule keeps its budget and review
bind across generations and forks (#983, #995, #998, #999). Delegation hands a
scoped, revocable resource between components, bounded at depth one (#456,
#589).
- MCP: `revl mcp proxy` gates an existing MCP server with no `.rvl` written,
`revl serve --http` and the MCP HTTP transport answer per-request operators
with SSE streams, and tools are disclosed in tiers with a `revl_verbs` index
(#1535, #554, #1557, #1505, #1746). Every mutating response carries its exact
undo, `step_back` reverts the last change, and speculation is the default with
one explicit commit (#1721, #1741). `revl_state` reports the six agent-loop
axes, `revl_counterfactual` replays a `revl_act` log under a what-if, and
`revl_swap` and `revl_change` carry a blast radius (#1759, #1763, #1874).
Every verb is callable by a function-calling model, and `revl_source`
enumerates the packaged stdlib (#2076, #2179). A swap or load that does not
succeed leaves the running composition serving (#1445, #1447).
- `fillSpec` v2 and scaffold: a crossing is a permission with its call-site
form, each hole names the grammar category of its fill, a hole whose crossings
span two capabilities is split, and a pure Unit method's fill is refused
(#1655, #1657, #1671, #1662, #1865).
- Replay and simulation: `revl replay` with replay-mode readiness,
counterfactual incident replay under an alternate policy, and `revl simulate`
policy-diff over a recorded WAL (#635, #849, #852). `revl goal` audits
termination contracts and reports blind spots (#452, #520, #593). `revl
analyze` searches a Petri net of the composition for a reachable dead state
(#399).
- A compositional blast-radius benchmark scores an agent on the six axes
`revl_state` reports (#1737, #1764).
- A cold `revl_change` withdraw is planned against the carried composition
(#2406, closes #2225).
- `revl_undo` still re-admits the target generation by a cold start, and now
says so: the undo dossier lists in `stateDropped.components` each running
component whose live state the rollback drops and the components the target
withdraws, with a note, and a forward call whose undo is `revl_undo` carries
an `undoNote` that says the same (#2422, closes #2346).
- A refusal made while a proposal is held names the held proposal. When the
running composition has a name that the held proposal removed, the message
lists what the proposal removed and how to proceed, and every such refusal
carries the `proposal` block (#2428, closes #2230).
### Placement and interop
- Per-component tier placement: a hot component compiles to rust or go while the
rest of the composition stays on py or ts, composed over the existing
cross-process seam with G2/G3/G4 checked over the whole manifest.
- The extern config coeffect: a document-global extern reads static config
resolved at plug time instead of shuttling identity through environment
variables, on py, ts, go, java, and rust. Config is static data, not a
capability channel, and a config field type is restricted to data heads.
- Embeddable gate: a two-layer gate API (a pure verdict surface plus a session
facade), fail-closed, that never admits what the reference compiler refuses.
It ships in three forms. `revl.gate` is the python library facade.
`crates/revl-gate` is the native rust library, generated from the self-host
sources by `tools/build_gate_crate.py` and committed so it builds with no
Python on the machine. `crates/revl-gate-wasm` packages that crate as a
WASI-P2 component (`revl:gate@1.0.0`) with an EMPTY import list, so the
verdict is a total deterministic function of its arguments, provable from the
artifact. Both native forms carry the same asymmetry: they can refuse, and
their non-refusing arm is `no-objection`, never an admission.
- `crates/revl-lsp`: the language server as a native rust binary (item 336
slices 1 and 2). It answers `definition` and the signature half of `hover`
through `revl_gate::symbols` with no interpreter on the path, and forwards the
reference front end's diagnostics verbatim, adding native refusals rather than
replacing anything.
- `revl deploy`: a deploy map and descriptor, attested admission, a coordinated
rollback, a deploy-admit runner with a stdio transport, and a cross-machine
leg over SSH with a pinned host key (#440, #451, #667, #744, #795, #803,
#811). The admission-gated deploy verb is exposed over MCP (#890).
- The sandbox isolation ladder: a wasm-cell rung, a microVM rung, a mixed-arch
container rung, and a seam transport with per-precondition refusals and an
approval channel across the boundary (#638, #678, #437, #511, #621, #610,
#817).
- Named instances: placement-aware spawn with a named handle, surfaced into the
manifest, with a `place` statement and a plan-time gate (#454, #530, #629,
#748). Ambient admission for a running composition: provision disjointness,
replacement and hand-off state, route and handoff rows on the native gate wire
(#599, #645, #1034, #1055, #1107). Composition layers fold a stack into the
row table, with per-root admission profiles in `compile_files` (#701, #637).
- Attested placement: `requires attested_tee` verifies an enclave against a
pinned attestation root, result receipts ride the call path, and a component
certificate accounts for the whole guarantee catalogue (#859, #964, #975,
#1023, #862, #973). A composition SLO contract with an observed half, a sample
floor and a declared denominator runs from inside the running generation
(`revl slo`) (#869, #1008, #1024, #1040).
- A2A: `import_a2a` binds the HTTP+JSON/REST transport, `through a2a` binds the
1.0.0 wire for the remote row, the four-op Task lifecycle is bound over both
entry points, and every generated crossing is correlated with its reply
shape-gated and its returns tainted `Untrusted` (#518, #594, #697, #774, #783,
#1030, #977, #1084, #630).
- The private peer pool: a declared object with a join admission gate, an
asymmetric key pair with rotation and revocation, dispatch to a member with a
one-result delivery ledger, member liveness (`revl pool probe`, `pool status
--require-live`), multi-file dispatch as one bundle, and promotion of a member
(#1277, #1290, #1425, #1467, #1541, #2178).
- Runtimes and artifacts: `revl bundle` has a one-file form (#490, #585).
`revl-lsp` bakes the private runtime into one distributed binary (#435, #738,
#802). `revl export client --lang ts` pairs with `revl serve --http` (#450,
#554). The Rust gate gained a manifest-aware `admit_into` and an admission
surface reachable only behind the refusal gate (#860, #987). Placement runners
serve only the keys and methods the spec exports and resolve a key in its
provider's realm (#1606, #1620, #1574). Realm labels are per `(realm, key)`
(#1549, #2063).
- Placement runners: an unconfirmed dispose is no clean teardown on rust, java
and ts (#2355). The go runner awaits each dispose in held-mode teardown before
it reports DOWN (#2338, closes #2336). An unsupported proxy service is fatal on
the rust runner (#2340, closes #2335).
### Security hardening
An adversarial review swept every shipped security feature across three waves
and found one recurring bug class: an authority-derivation fold that visits one
crossing surface and misses an alternate crossing, so a capability is reached
but not seen. Every instance was closed. Representative fixes:
- Taint (provenance): endorse no longer launders a foreign origin, whether the
origin is argument-derived or minted inside the endorsing body.
- The untrusted-author admission profile now refuses a host-extern reach through
the whole transitive module closure, not only a direct import.
- The MCP server no longer treats the driving agent as a trusted host-code
author. Source that arrives over the transport compiles under the
untrusted-author profile, so an inline `@py` body can no longer be loaded and
run. `revl mcp serve --author-trust`, `--provider` and `--grant` are the
operator's controls, and the trusted mode stamps every class-(c) ticket with
the unreviewed host bodies a yes would also let run.
- Path arguments to the MCP verbs are jailed to the operator-sanctioned roots.
They were a filesystem oracle over every path on the machine, and a restore
plus snapshot round-trip returned an arbitrary file's whole content.
- The host-file and host-ref jails gained a realpath-containment gate at every
install-origin resolution site (the compiler and the bundle verifier), so a
path that escapes the compile root cannot forge a first-party origin.
- The approval gate uses one grant-coverage predicate for both auto-approve and
revocation, follows the spawn seam as well as the service seam, and installs
the session owner on every call.
- The atomic compare-and-set guards its inverse on the result across all tiers,
so a lost race cannot delete the winner's entry.
- A witnessed inverse that reaches an emission through a plain function wrapper
is refused, keeping teardown emission-free.
- The witnessed-fs workspace jail routes every path family through the guard,
not only the ones that already did. An inverse's SOURCE endpoint is confined
and restricted to a sidecar the workspace itself produced (a rename removes
what it names, so an unchecked source stole outside files rather than merely
overwriting them); the sidecar directories are resolved and refused when a
symlink is planted under their name; a hardlinked target is refused rather
than written through; and every mutation runs through directory fds walked
down from the root with `O_NOFOLLOW`, closing the check-to-syscall window.
The families are enumerated in the guard and scanned in the `@py` bodies, so
a fifth cannot be added silently.
- The ts tier's fs host carries the same pass. `backends/typescript/revl_fs_ts.ts`
had held every one of those escapes untouched while the py fix shipped: an
unconfined inverse source that stole and destroyed outside files, sidecar
directories a planted symlink could redirect outside the root, no link-count
check, and a name-based write a concurrent writer could divert. It now states
the same `PATH_FAMILIES` enumeration, restricts an inverse's source to a
sidecar this workspace produced, refuses a symlinked sidecar directory by
type, refuses a multiply-linked target, writes only through a verified fd, and
refuses a write whose name has parted from the inode that was written. The
enumeration is scanned with the TypeScript compiler API, the ts peer of the
`@py` body scan. One difference is stated rather than papered over: node
exposes no `*at()` syscall, so the check-to-syscall window is narrowed with an
`lstat` component walk plus `O_NOFOLLOW` and an fd-identity check, not closed
the way the directory-fd walk closes it on py.
- The distribution seam refuses a resource crossing on same-tier as well as
cross-tier process boundaries, and the wire encoder fails closed on any value
that is not scalar, list, dict, or a declared record or ADT.
- A network (TCP+mTLS) seam refuses replays. The peer allowlist pinned who may
call and deliberately did not dedup, so an admitted peer's request could be
captured and re-delivered and the provider dispatched it again. The reason
recorded for that (a replay ledger needs the per-boot secret a
cross-composition caller can never hold) was wrong: dedup needs an identity
to scope the key by, and mutual TLS has already proved one. Freshness is now
keyed on the identity the handshake proved, read off the peer certificate and
never off the payload a replayer controls, so it needs no shared secret, and a
consumer under another conductor is admitted exactly as before. The ledger is
bounded per identity, and what an eviction costs is stated rather than hidden.
Seams report `peer-bound` where they earned it.
- The node bridge seals a correlation envelope. It had no `correlation`
parameter at all, so a placement disarmed a provider's guard entirely rather
than refuse a caller that could not satisfy it: one node consumer left a py
provider unguarded. The ts bridge now seals the same HMAC over the same
canonical bytes, proven against the real guard over a real seam.
- A boundary policy rule now selects a directly emitted extern by the capability
token it declares. `extern emission[db] fn pg_write` put `pg_write` in a component's
reach while the register floor, the approval gate and a bound secret all keyed the
same crossing `db`, so `capability db requires register keyed` selected nothing on a
composition whose only `db` crossing was a direct emission: an operator wrote a floor
and silently got less. Reach carries the declared scope now, and the extern's name is
no longer a second spelling for the same crossing. `revl audit` renders the scope on
the host-code line, and the full table of which spelling a rule selects is in
docs/boundary-policy.md.
- Approval expiry is monotonic and irreversible. The session clock is anchored
to a monotonic source rather than read from the wall clock on every check, and
a grant that has been observed expired is latched dead, so nothing that moves
the machine's notion of "now" can hand back an expired grant for its remaining
uses.
- `revl mcp serve --approval-record-values` now defaults to `withheld`: a
crossing whose resource target came from a caller argument is not written into
the durable cross-session approval log unless the operator opts in with
`bound`. The cost of the default is that such approvals no longer fold into a
distilled auto-approve rule, and the distiller now says so and names the flag
when it declines. Author-written literal targets are recorded either way.
- The TypeScript emitter escapes author text that reaches an emitted comment.
A `config` field's default is rendered into a `/** default: ... */` doc line,
and the string escaping it went through is the one a string LITERAL needs.
A comment has no escape syntax at all, only sequences that end it. The doc
line now goes through a comment escaper that breaks both of those (a line
terminator, and `*/`), so a default can no longer close the comment and put
the rest of itself in code position. Emitted string literals, identifiers and
template literals were already routed through their own escapers and are
unchanged; no golden bytes move.
- `revl fmt` proves meaning preservation for every file it writes. Its
equivalence gate had two holes that met in the middle: the IR comparison
compiled with no module resolution, so every `use`-bearing file skipped it,
and the fall-back it landed on compared the formatter's own trivia scanner
with itself, which cannot fail no matter how wrong that scanner is. The
scanner had drifted from the lexer, so a `use`-bearing file with a
triple-quoted string, a `0x`/`0b`/`0o` literal, a `_` digit separator, a
`'...'` string, a `\"` escape or a `}` inside a host body was silently
rewritten into a different program. The gate now lexes both texts with the
reference lexer and refuses on any difference, before anything else and
whatever the file contains; the IR comparison resolves imports for an on-disk
file so it actually runs; and the scanner calls the lexer's own number,
string and brace-matching routines instead of a second copy of its rules.
Across the 888 `.rvl` files in the tree the formatter now changes the
compiled meaning of none.
- Declared secrets are redacted on every tier: the secret registry covers byte
secrets, container secrets, Map keys and values, and the escaped rendering of
a value, with walks bounded by path instead of recursion. Runner log channels
funnel behind confidential redaction (GHSA-4x4q-296m-9x9x) (#391, #432, #894,
#905, #911, #912, #916, #918, #920, #923, #924, #925, #926, #931, #835). Bound
secrets are injected into extern bodies on ts as on py, and rotate after plug
(#1953, #2047).
- Effect composition is decided per statement (A1), and emission obligations
resolve across a service-typed arrow parameter (#394, #395). The parser and
gate bound source and statement nesting so deep input is refused, not crashed,
and refuse an unknown top-level IR field or schema revision by name at the
admission boundary (#566, #658, #1452, #489, #578, #607).
- Host acquisitions undo as their release: an acquisition's undo must be its
release on the bound handle, an extern acquire's site undo its declared
inverse, and a provide-method host write's undo its inverse with provenance.
`no_residue` is no longer green when a bracket's undo did not reverse its
write, and session teardown checks that every host resource was released
(#1879, #1885, #1961, #1966, #2054, #1869).
- The approval gate hashes host code reached as a function value, discloses a
declared-untrusted argument on the ticket, carries each capability's crossing
ceiling, and treats a witnessed call as class (a) only where its inverse is
registered (#1546, #1991, #1819, #2045). A swap ticket names the candidate's
host code, and component leases fence load and unload and bound a lease's TTL
(#1450, #906).
- Taint survives more boundaries: a persisted origin survives the host round
trip, the IR keeps a service signature's `Untrusted`/`Trusted` qualifiers, a
routed operation's parameters are stamped as inbound request values, and
qualifiers are preserved across the manifest boundary (#1993, #1955, #928,
#660). Authority does not leak across trust boundaries: an agent-authored
successor inherits no operator authority, a proposal does not inherit a
standing authority by taking a reviewed component's name, and a replacement's
reach is clamped to the inherited bound (#661, #1101, #712).
- Seam hardening: a hostile-wire TCK over the sealed seam envelope, a provider
failure funneled before it reaches the consumer on every tier and thread, the
consumer-side error channel scrubbed, and a wire `$kind` resolved against the
declared cases only (#580, #666, #899, #909, #913, #910).
- The witnessed fs gained original write receipts with expected-before guards
(`revl.fs.write`), identity checks of the preimage and garbage sidecars inside
restore and unrm, and an early refusal on hosts without dir-fd support (#529,
#606, #1037, #1052, #1070, #2020).
- The shell classifier refuses a backslash-newline continuation and a `$` or
backtick inside double quotes (#934, #953). Archive extraction in the unload
survey is contained, `truc` names are jailed to `trucs/`, and an in-memory
compile jails assets and refs lexically (#2124, #846, #1777). Network-exposed
signing is refused without `cryptography` (#1534). A model-routed component is
accounted for what it can reach, not its token spelling (#2186).
- HTTP faces: requests are framed by a single agreed `Content-Length`, a
duplicate credential header is visible, and no authority value is decoded from
the wire (#901, #914, #1505).
### Operations
- E-Stop (item 443): the operator's emergency halt, as a third verdict `halted`
alongside `committed` and `aborted`. `revl estop` arms an idempotent latch,
`revl estop --report` reads it back, and `revl run --estop-latch FILE` honours
it; `revl_estop` and `revl_estop_report` are the MCP forms. The price is
stated, not hidden: every registered entry is left stranded and every acquired
handle stays held, and `revl recover --wal` is how the books are settled.
- `revl changelog`: the release note derived from the structural delta between
two compositions, every line traceable to a differ fact (item 261).
- `truc`, the component manager (`revl truc`, `src/revl/truc/`): eight
components plus a bootstrap that assembles itself through the gate on every
invocation.
- E-Stop on every tier: ts, node, java, go, rust and wasm report or honour the
latch, the py runtime stops every crossing before its host body from any
thread, and the ts tier carries an in-flight crossing registry and halt
inventory (#453, #514, #611, #773, #779, #769, #1518).
- Crash recovery: the WAL stays open for the run's life, a torn tail is sealed
before every append, run-complete is scoped to its own interval, recovery
requires a settled WAL and records emission outcomes, and a provider's
emission made while serving a cross-process call folds into the caller's
crossing (#568, #563, #650, #718, #2221, #1890). Two-phase admission has
durable stages and forward recovery (#513, #619, #699). Session teardown is
awaitable and audited, with original-resource settlement (#533, #656).
- The gate/reference census is published as a generated artifact with
outsider-recomputable identities and a verifier, and the pinned three-host
framework benchmark is published (#1306, #1421, #1469, #2156, #1415, #2154).
- `revl doctor` resolves the toolchain reproducibly and reports the revl version
through one definition that attestations also stamp (#790, #2267, #2279).
`revl mcp serve` re-execs under the runtime venv or announces and refuses the
runtime verbs (#1710). `revl run --watch` keeps the ambient provisions across
a reload (#1593). `revl run` names the failing lifecycle stage on every error
(#768).
- Licensing: AGPL-3.0 for the compiler, gate and tooling, MIT for what ships in
your program, with a trademark policy (#1237).
- Front-door docs for the release candidate (#2415, closes #2409, #2410, #2411,
#2412): the quickstart installs from a clone (`pip install -e .`) and no
longer points at the unrelated `revl` project on PyPI, the self-host numbers
come from docs/conformance.md, the commands and MCP reference documents
`revl export openapi`, `slo --seal`, `deploy --bundle` and
`import openapi --undo`, and public docs no longer name private downstream
projects.
- Every `--backend` flag and tier alias map reads one tier list in
`src/revl/tiers.py`, so a tier spelled one way is accepted or refused the same
way by every command. `revl deploy --backend` is now validated (#2299, closes
#2205).
- `revl mcp serve --print-config` prints the client config block for the first
interpreter that imports both revl and cordis, and `revl doctor` gets a
matching row (#2318, closes #2281).
- The WAL stops its writers on a failed fsync on go, rust, java and wasm
(#2354). Service compensations are journaled, and unsynced records are cut on
go, rust and java (#2372, closes #2345).
### Live systems
`revl swap`, the runtime why-oracle, and `revl apply` run from a tagged clean
checkout as scripted exit tests.
### Known issues
Open at this release: the compiler admits a program that the target toolchain
then rejects, or that it handles differently from the reference.
- #2325 (java): generic variants, `Unit` type arguments and `Set`/`Stream`
heads emit invalid Java.
- #2328 (self-host rust): a type named `Box` is not renamed to `Box_` as the
reference does.
- #2344: a `@py` host body reaches the runtime through `sys.modules` although
`import runtime` is refused.
### Internal
Regeneration, CI, test tooling and roadmap-currency work (the light PR runs and
merge-queue CI of #1679, the root-suite sharding, the affected-tests selector,
the census and ledger records, the site and playground wheel refreshes, and the
main-health fixes between them) is not itemised; it changes no behaviour of the
language or its tiers. Recent examples: the env-gate credit guard (#2304), one
loader per backend emitter (#2306), a cached test listing for the affected-tests
selector (#2307), the pre-merge run of every CI-only suite (#2310), a shared
compile of the self-host lowering module across tests (#2314), and a main-red
fix to a paths-ignore control (#2330). Later examples: the proof that
skip gates are set on the pre-merge path (#2393, closes #2210), the pure-read
guard tests in lint (#2365, closes #2333), a selector rule that re-runs the
corpus guards (#2357), the test-listing cache in the tools (#2326), and the
shared emitter loader for test.py, gate.py and run_wasm (#2323).
### All pull requests in this release
Every pull request squash-merged into main from the v2.0.0 point (commit a73dbbfa7, the rewritten form of 04fa2a00b) through origin/main at 631b77a79 is listed here, in ascending order.
106, 114, 149, 154, 157, 199, 201, 205, 206, 208, 209, 212, 214, 215, 216, 219, 220, 221, 222, 227,
229, 230, 231, 232, 235, 243, 246, 251, 265, 273, 288, 289, 292, 302, 306, 308, 321, 333, 334, 351,
366, 376, 378, 379, 380, 382, 386, 391, 394, 395, 396, 398, 400, 409, 411, 412, 413, 451, 453, 454,
456, 459, 460, 462, 472, 473, 474, 481, 484, 487, 488, 489, 490, 491, 492, 493, 494, 496, 497, 498,
499, 500, 501, 502, 503, 504, 505, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519,
520, 521, 522, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 554, 555, 556, 557, 558, 559, 560,
561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 573, 574, 575, 576, 577, 578, 580, 581, 582,
583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 599, 600, 601, 602, 603, 604,
605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 621, 629, 630, 631, 632,
633, 635, 636, 637, 638, 639, 645, 646, 647, 648, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659,
660, 661, 662, 663, 664, 666, 667, 672, 674, 675, 676, 678, 679, 680, 682, 684, 688, 689, 690, 691,
692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 711, 712, 713, 714, 715, 716,
717, 718, 726, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 740, 742, 743, 744, 745, 746,
747, 748, 749, 750, 753, 754, 755, 760, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773,
774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793,
794, 795, 796, 798, 800, 802, 803, 804, 806, 807, 808, 809, 810, 811, 816, 817, 818, 828, 829, 832,
833, 834, 835, 836, 837, 838, 839, 840, 842, 845, 846, 847, 848, 849, 850, 851, 852, 853, 855, 856,
857, 859, 860, 861, 862, 863, 864, 865, 866, 869, 870, 871, 874, 875, 877, 878, 879, 881, 882, 883,
884, 886, 888, 889, 890, 891, 892, 894, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907,
908, 909, 910, 911, 912, 913, 914, 915, 917, 918, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929,
930, 931, 932, 933, 934, 936, 942, 943, 944, 945, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956,
958, 959, 960, 961, 962, 963, 964, 965, 966, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978,
980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000,
1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1018,
1019, 1020, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1030, 1031, 1032, 1033, 1034, 1035, 1037,
1039, 1040, 1042, 1043, 1044, 1048, 1049, 1050, 1051, 1052, 1053, 1055, 1056, 1057, 1058, 1059,
1060, 1061, 1063, 1064, 1066, 1068, 1069, 1070, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079,
1080, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097,
1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1109, 1110, 1111, 1113, 1114, 1115, 1117,
1118, 1119, 1121, 1122, 1124, 1125, 1126, 1128, 1131, 1133, 1134, 1135, 1137, 1138, 1139, 1140,
1141, 1143, 1144, 1146, 1147, 1149, 1150, 1152, 1154, 1155, 1156, 1159, 1160, 1162, 1163, 1164,
1165, 1168, 1170, 1171, 1173, 1174, 1176, 1177, 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185,
1199, 1202, 1208, 1210, 1213, 1214, 1216, 1218, 1220, 1226, 1227, 1230, 1231, 1232, 1237, 1238,
1239, 1241, 1242, 1243, 1245, 1247, 1250, 1256, 1257, 1258, 1260, 1262, 1264, 1269, 1270, 1271,
1273, 1275, 1277, 1280, 1281, 1282, 1283, 1284, 1286, 1287, 1290, 1292, 1294, 1295, 1297, 1301,
1304, 1306, 1312, 1313, 1314, 1317, 1322, 1325, 1326, 1329, 1330, 1335, 1346, 1349, 1350, 1351,
1352, 1353, 1354, 1355, 1357, 1360, 1361, 1362, 1363, 1364, 1365, 1372, 1374, 1375, 1378, 1380,
1383, 1384, 1386, 1389, 1390, 1391, 1392, 1395, 1396, 1398, 1401, 1402, 1404, 1405, 1408, 1409,
1411, 1412, 1413, 1414, 1415, 1416, 1417, 1419, 1420, 1421, 1422, 1423, 1424, 1425, 1426, 1428,
1429, 1432, 1436, 1439, 1440, 1441, 1442, 1445, 1447, 1448, 1450, 1452, 1456, 1459, 1466, 1467,
1468, 1469, 1471, 1476, 1478, 1482, 1483, 1485, 1490, 1493, 1495, 1501, 1505, 1514, 1516, 1518,
1523, 1524, 1530, 1531, 1532, 1533, 1534, 1535, 1537, 1538, 1541, 1542, 1544, 1546, 1548, 1549,
1552, 1555, 1556, 1557, 1558, 1563, 1565, 1568, 1570, 1571, 1573, 1574, 1576, 1578, 1579, 1584,
1586, 1590, 1591, 1593, 1594, 1595, 1596, 1597, 1598, 1602, 1604, 1606, 1612, 1615, 1616, 1617,
1618, 1620, 1622, 1623, 1624, 1626, 1628, 1629, 1630, 1635, 1636, 1637, 1638, 1640, 1642, 1645,
1649, 1650, 1651, 1653, 1655, 1657, 1662, 1663, 1665, 1666, 1667, 1668, 1670, 1671, 1673, 1674,
1679, 1687, 1689, 1710, 1711, 1712, 1718, 1719, 1721, 1730, 1731, 1732, 1737, 1741, 1744, 1746,
1748, 1749, 1753, 1758, 1759, 1761, 1763, 1764, 1766, 1767, 1770, 1772, 1773, 1775, 1777, 1778,
1782, 1783, 1785, 1787, 1788, 1789, 1790, 1791, 1794, 1795, 1796, 1797, 1799, 1803, 1804, 1815,
1816, 1819, 1824, 1825, 1827, 1830, 1831, 1833, 1843, 1852, 1853, 1854, 1856, 1858, 1860, 1863,
1865, 1866, 1867, 1868, 1869, 1870, 1871, 1872, 1873, 1874, 1875, 1877, 1878, 1879, 1880, 1882,
1883, 1884, 1885, 1887, 1890, 1891, 1893, 1898, 1906, 1907, 1916, 1917, 1919, 1922, 1925, 1928,
1929, 1930, 1934, 1941, 1943, 1947, 1949, 1951, 1953, 1955, 1956, 1957, 1958, 1959, 1960, 1961,
1962, 1964, 1966, 1967, 1969, 1970, 1971, 1973, 1974, 1975, 1976, 1977, 1978, 1986, 1988, 1989,
1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
2006, 2010, 2011, 2013, 2015, 2017, 2018, 2020, 2021, 2022, 2023, 2024, 2030, 2039, 2040, 2041,
2043, 2045, 2047, 2048, 2049, 2050, 2051, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2060, 2061,
2062, 2063, 2064, 2065, 2066, 2068, 2069, 2070, 2071, 2072, 2075, 2076, 2078, 2080, 2081, 2082,
2101, 2104, 2106, 2107, 2109, 2112, 2113, 2116, 2119, 2120, 2121, 2122, 2124, 2126, 2128, 2129,
2130, 2133, 2134, 2135, 2136, 2137, 2138, 2139, 2140, 2141, 2142, 2143, 2144, 2145, 2148, 2149, 2152,
2154, 2155, 2156, 2157, 2158, 2159, 2161, 2162, 2163, 2164, 2168, 2169, 2170, 2171, 2172, 2174,
2175, 2176, 2177, 2178, 2179, 2180, 2182, 2185, 2186, 2220, 2221, 2226, 2227, 2228, 2233, 2235,
2236, 2238, 2242, 2243, 2244, 2245, 2247, 2248, 2249, 2251, 2253, 2255, 2256, 2258, 2261, 2265,
2266, 2267, 2268, 2269, 2270, 2271, 2272, 2273, 2274, 2278, 2279, 2282, 2283, 2288, 2290, 2291,
2292, 2293, 2294, 2295, 2298, 2299, 2300, 2301, 2302, 2304, 2305, 2306, 2307, 2309, 2310, 2312,
2314, 2318, 2319, 2320, 2323, 2326, 2327, 2330, 2331, 2338, 2340, 2354, 2355, 2357, 2365, 2369,
2370, 2372, 2375, 2376, 2377, 2387, 2388, 2392, 2393, 2395, 2398, 2400, 2404, 2406, 2415, 2422,
2425, 2426, 2427, 2428
## 2.0.0
The typed-effect core, the six emitters, and the reference IR. See the roadmap
history for the 2.0 line.
## 1.0.0
Initial release.