Repository navigation
Releases: Chelis-Lang/chelis
Release list
v0.19.1
[0.19.1] - 2026-10-06
Fixed
-
Public Chelis toolchains and Reef packages install from GitHub releases without
a GitHub token. Private releases continue to useGITHUB_TOKENorgh auth token.
See #2840. -
The checker processes nested aggregate result constraints together, avoiding repeated traversal when checking long list literals. See #2975.
-
Reef builds with an exact package pin no longer fail because an excluded version in the local registry has an unreadable shell format. Errors for an unreadable matching candidate name its version and matching requirement. See #3290.
-
chelis tide mcpuses newline-delimited JSON instead of LSP-style
Content-Lengthheaders, so standard MCP clients can initialize, discover tools,
and call them over stdio. Notifications no longer receive spurious error
responses, and the server answerspingrequests. -
The release E2E softmax probe states its tensor dtype explicitly, so it remains
valid under the tensor literal ingress contract.
v0.19.0
[0.19.0] - 2026-10-06
Changed
-
BREAKING: Keep Surf pipes as syntax sugar;
fmtpreserves authored pipes, whiledeepandsurfemit normalized calls. Ungrouped mixes with other operators, postfix access, ascriptions, updates, or open-ended forms now require parentheses. Previous Deep/cache/package payloads must be regenerated.Silent numeric value change:
0.1 |> cast(f64)now adopts the f64 cast target before rounding, exactly likecast(0.1, f64). Its bits change from3fb99999a0000000to3fb999999999999a. To retain the former value, write0.1f32 |> cast(f64). Before upgrading a shell or downstream repository, runchelis migrate pipes --baseline-compiler OLD --inplace PATH...; it proves the complete expanded Deep unchanged before replacing the batch.--checkaudits without writing; explicit--keep-goingchecks or migrates independently proven files and reports every failure with a nonzero exit. -
BREAKING: A numeric literal's dtype is written at its site: it comes from the literal's suffix, or from a dtype-stating construct that directly contains it when the literal's kind admits the stated dtype, or else from the
i32/f32default, and never from a callee's signature or anything further away. The closed set of constructs is a declaration (the declared type of the binding ordefresult that the literal, or its negation, initializes, and a declared tensor type's element dtype for every literal element of a bare bracket initializer), acastwhose operand is the literal, and a new optional dtype argument ofto_tensor. Expression ascriptions such as(1.0 : f64)and declaredListtypes still only check.- Scalar declarations now bind their literal:
x: f64 = 1.1is exactly1.1f64,def f() -> i8 = -128binds thei8minimum, andx: i64 = 3000000000is accepted. Previously each was rejected as a dtype mismatch. A declaration states the dtype only of a literal that is its whole initializer:x: f64 = neg(1.1)andx: f64 = if c then 1.1 else 2.2stay rejected, and so do type aliases such asx: P = 1.1. to_tensor(xs, p)takes an optional dtypep, a primitive in an argument position that always names a dtype, never a value. It statespfor every unsuffixed literal element of a bracket-literalxs, soto_tensor([1.1, 2.2], f64)builds the exactf64values. It never converts: a leaf dtype other thanp, such asto_tensor([1.0f64], f32), is a type error. A dtype-bounded binderpis accepted as the dtype argument over leaves that already have dtypep, such asto_tensor([cast(1.1, p)], p). An unsuffixed literal that a binder dtype argument or a declared binder type would bind, as into_tensor([1.1], p)ordef f[p: Float](x: p) -> p = 1.1, is rejected with a diagnostic that namescast(1.1, p); see #3148.- An unsuffixed literal element of a
to_tensorcall without a dtype argument is a type error. Writeto_tensor([1.1, 2.2], f32)(ori32for integers) to keep the previous value, or state the dtype you mean. In particularcast(to_tensor([1.1, 2.2]), f64), which rounded both elements throughf32, is rejected and its diagnostic names bothto_tensor([1.1, 2.2], f64)andcast(to_tensor([1.1, 2.2], f32), f64)(#3080).xs: tensor[2, f32] = to_tensor([1.1, 2.2])is the same error; the bare bracketxs: tensor[2, f32] = [1.1, 2.2]keeps taking the declared dtype. AListbound first keeps its defaults:xs = [1.1]; to_tensor(xs)is accepted. In Deep, an untyped numeric literal element of ato_tensorargument is rejected. - A cast whose unsuffixed literal operand its primitive target binds desugars to that literal alone, with no
castnode:cast(1.1, f64)has exactly the Deep of1.1f64, andcast(3000000000, i64)that of3000000000i64(#3135). A negated operand folds into one signed literal at the target, socast(-128, i8)is thei8minimum, which has no suffixed spelling. Values are unchanged. Every other literal cast keeps its node and converts:cast(1.1f32, f64)still widens thef32value,cast(2.0, i32)converts thef32default,cast(1, bool)istrue, and a cast to a dtype binder converts at the binder's members the literal's kind does not admit. to_tensoris a reserved name. Binding it as a top-level or packagedef,sig, ormacro, a function, lambda, or macro parameter, a block or pattern binder, or an imported name is rejected asReservedNamein Surf and in Deep, inside a reef package too. This replaces the refusal of a declared tensor literal under a localto_tensorbinding, and it rejects the previously accepted programs of #3152 and #3167.chelis surfprints a literal without its suffix only where re-reading the output binds it at the same dtype, and always suffixes a cast's literal operand, so a Deep-to-Surf round trip keeps every literal's dtype and everycastnode (#3165).
- Scalar declarations now bind their literal:
-
BREAKING: A macro definition must reference every one of its parameters in its body.
chelis check,eval, andbuildnow reject amacrowhose body never uses a parameter, naming the macro and the parameter, whether or not the program calls the macro; a reference inside the scope of a body binder of the same name, such as afnparameter, does not count. Previously the argument for such a parameter was dropped during expansion, before name resolution, type checking, effect inference, and linearity checking, somacro first(a, b) = awithfirst(1i32, never_declared),first(1i32, print("boom")), or a reusedkeychecked clean and ran without the argument's effect, and a wrong-arity macro call inside the argument was never rejected. Rewrite such a macro without the unused parameter.In a single file or an entry module, a macro parameter now shadows an imported name of the same spelling inside the macro body, as it already did in library modules. Previously
import Std.Scalar (abs)withmacro apply(abs, x) = abs(x)bound the body'sabsto the import, soapply(neg1, 5i32)calledStd.Scalar.absand droppedneg1. See #3266. -
The canonical project reference describes Chelis's architecture and package
boundaries in present tense. See PR #3274.
Fixed
-
Rank-polymorphic
chelis evalcalls now enforce checked local tensor extent claims in execution order, including claims after the returned value is produced. Local claim axes account for expanded rank spreads in both eval and compiled C. See #3092. -
The checker rejects constructor and tuple patterns that cannot match the
scrutinee type, including wrong nominal constructors, non-tuple scrutinees,
and tuple arity mismatches. See #3228. -
Compiled C programs now respect the scope of match-arm pattern binders when a call is inlined. A pattern that re-binds a parameter's name, in any constructor, record, tuple,
@or nested pattern, keeps that name for its guard and body, and a pattern binder no longer captures a name read by a function passed in or defined around the match. Previously such programs could print a different result fromchelis eval. See #3229. -
A positional pattern over a record now binds the record's declared fields in the tensor graph that
gradandvmaplower to, whatever order the record literal wrote its fields in. Previouslymatch P { b: ..., w: ... } with { | P(w, b) => ... }boundwandbto each other's values there. As a result,gradreturned the gradient of a different function,vmapreturned wrong forward values in bothchelis evaland compiled C, and compiled C returned a wrong gradient with respect to a record-typed parameter built from such a literal. Field expressions still evaluate in written order. A lowering that cannot determine a record's declared field order now rejects such a pattern instead of guessing. See #3230. -
The checker rejects calls that use one type binder as both a tensor precision and an incompatible ordinary type. Cyclic precision constraints also report a type error. See #3233.
-
chelis proveno longer certifies a false property whose binder or module name is spelled like a solver symbol the prover generates, such as__erf_abs_0,__contract_std_normal_cdf_0, or__contract_std_quantile_0. Generated symbols are now chosen fresh against every name in the goal, so such a name stays an independent variable and the property is refuted or left unsupported; a counterexample may show the generated symbol with a numeric suffix. A solver goal that declares one variable twice is rejected instead of merging the two variables into one. See #3236. -
A macro call must supply exactly one positional argument per macro parameter and no named argument, and a macro definition may not repeat a parameter name.
chelis check,eval, andbuildnow reject a call with too few or too many arguments, naming the macro and both counts; a call that passesaccumulator=, naming the macro and the argument; and a repeated parameter, naming the macro and the parameter. Previously a missing argument let its parameter resolve to a same-named binding at the call site, a surplus argument was dropp...
v0.18.13
[0.18.13] - 2026-10-05
Added
-
BREAKING: A type binder's dtype bound may now be an explicit set of dtypes, written
[p: {f32, f64}], beside the existingFloat,IntandNumericfamilies. A set admits exactly the dtypes it lists, so a declaration can exclude a member its family would admit — which is what lets a generic body hold a constant that one narrow family member cannot represent, such as a literal that rounds to infinity atf16underFloat. A family still denotes whatever the active dtype set admits into it and widens when a dtype is activated; a set never does, so a set enumerating a family's current members is not that family. Breaking: the package-shell envelope moves to format version 6 because a published scheme can now carry a set-valued type-variable domain, so shells built by earlier compilers must be rebuilt. See #2443. -
Compiled C runs every host-runtime operation that
chelis evalruns, with
the same result or failure in the same effect order:tensor_scanover
scalar states,process_run,clock_wall_readandclock_monotonic_read,
round_to, the CSV builtins (parse_csv,to_csv, and thecsv_*
accessors), and thetest_assert*assertions. Each runs through one
definition in the runtime, shared by both lanes. A program that uses one of
them, or only defines a function that does, no longer failschelis build
with an "eval/test lanes only" or host-only error, and an uncalled assertion
compiles to working code rather than an abort stub. Tensor states for
tensor_scanremain blocked on the checker
(#2999). See
#1297. -
Std.Datetime.Businessadds business-day calendars tochelis-std. ABusinessCalendarcombines aWeekmaskof business weekdays, a holiday list, and an inclusive horizon. It answers only from the days of that horizon, and a query whose answer depends on days outside it failsdomain. The module providesis_business_day;business_day_rollwithUnadjusted,Following,Preceding,ModifiedFollowing, andModifiedPreceding;business_day_offset, which first rolls a non-business start under a requiredNonBusinessStartpolicy and then moves; half-open, antisymmetricbusiness_day_count; andbusiness_in_allandbusiness_in_anyfor settlement and union calendars. The constructor, the queries, and the combinations each have atry_form, and vectorized forms of the queries work overDates[n]columns. A scalar query reads O(log h) holiday-list elements for h holidays, whatever the horizon's length. See #2860. -
Std.Datetime.Columnsadds vectorized forms ofStd.DatetimeoverDates[n]
andInstants[n], and aDurations[n]column. They read date fields, build
dates from fields, add days and months under aDayOverflowpolicy, compare
columns, parse and print date text, convert unit counts under aTimeUnitand
aRounding, add and measure durations, round instants to an increment, read
dates at an offset, and give anf64time axis. Each callable agrees with its
scalar twin at every element; a failing call names the lowest failing element,
and the maskedtry_forms return a validity mask instead of failing. See
#2861. -
Std.Datetime.Zoneadds time zone rules as values.time_zone_from_tzifreads a TZif file (RFC 9636, versions 2 to 4) from bytes the program supplies, including its POSIX TZ footer rule;time_zone_fixedandtime_zone_utcbuild fixed zones.Zonedvalues convert local readings with a requiredDisambiguation(EarlierInstant,LaterInstant,CompatibleInstant,RejectNonUniqueLocal) for daylight saving gaps and folds, add durations on the instant line and periods on the wall clock, and read and write RFC 9557 text, resolving a written offset with a requiredOffsetConflictpolicy. Text whose offset isZ,zor-00:00, which RFC 9557 reads as a UTC time with an unknown local offset, resolves to that UTC instant under every policy. A zone whose footer is empty failsdomainfrom its last transition on instead of extrapolating. See #2862. -
Std.Datetime.Clockreads the host clocks.clock_now()returns the wall-clock
Instant, andmonotonic_now()returns an opaqueMonotonicInstantfrom a
clock that never runs backwards.monotonic_until(a, b)is the exactDuration
fromatob. It is the only datetime function that takes a
MonotonicInstant, which does not convert to anInstant. Both reads carryIO, so a caller declared
withoutIOcannot call them. A failed read reports
clock_wall_read: io: <detail>orclock_monotonic_read: io: <detail>, naming
the underlying builtin. The module is separate fromStd.Datetime, so nothing
inStd.Datetimereads a clock; any code that reads one carriesIO.chelis eval,
chelis test, and compiled C run the clocks
(#1297).
See #2863. -
Std.Decimaladdsdecimal_to_f16anddecimal_to_bf16, which round a decimal's exact value once, to nearest with ties to even, directly to f16 or bf16, subnormal results included. Castingdecimal_to_f32tof16orbf16rounds twice and can differ:decimal("1.00048828125000001")converts to the f161.0009765625, while its f32 image is an exact f16 tie that the cast rounds to1.0. A decimal of magnitude 65520 or more converts to the f16 infinity of its sign, and a negative decimal that rounds to zero converts to negative zero; neither conversion fails, and every decimal is finite in bf16. The module's laws (the text round trip, commutativity ofdecimal_addwithdecimal_subinverting it,decimal_to_f64(decimal(t)) == to_float(t), idempotentdecimal_round, and the i64 round trip) are stated aschelis proveproperties inpackages/chelis-std/properties/decimal.ch. spec/04 §1.1.1 now decides that exporting aStd.Decimalvto a declareddecimal128ordecimal256precisionpand scalesfailsdomainwhen|round(v, s) · 10^s| >= 10^p, never saturating or truncating. See #2996, #2946, and #2920. -
modnow accepts two floats of one dtype, scalars or same-shaped tensors, and computes Cfmod, as [05-OP-64] specifies: the exact remainder with the quotient truncated toward zero, so the result has the dividend's sign.mod(x, 0), an infinite dividend and a NaN operand give NaN, and an infinite divisor returns the dividend.f16andbf16compute atf32and narrow once, which is exact.chelis evaland compiled C agree. Differentiating throughmodis refused at every dtype with a diagnostic that names the jump in its truncated quotient. See #626. -
cast_saturate(x, T)andcast_wrap(x, T)complete the named lossy casts besidecast_trunc.cast_saturatetakes a signed integer or float and returns a signed integer: a float is truncated toward zero, then the value is clamped to the target range,-infand+infgive the minimum and maximum, and NaN trapsDomain([05-OP-23]).cast_wraptakes a signed integer and returns the target-width two's complement value without trapping ([05-OP-24]). Both work on scalars and on owned or borrowed tensors, including thex |> cast_saturate(i8)pipe stage, and refusegrad.chelis evaland compiled C agree on every admitted source and target pair, and compiled C now also convertscast_truncover a tensor computed on the host instead of refusing it. HIP builds refuse all three named casts until device traps exist.cast_saturateandcast_wrapare now reserved words, and the serialized operation graph moves to schema 25, wherenamed_castwith amodereplaces thecast_truncoperation. See #759. -
chelis lane-checknow compares the complete evaluator and compiled-C stdout of deterministic programs and reports divergences, failures, and provenance as versioned NDJSON. A locked native Linux x86-64 Nix check supplies the authoritative exact-output acceptance gate; local host-toolchain runs remain diagnostic. See #763. -
A source-derived C support inventory distinguishes builtin exclusions from
stdlib routes and partial capabilities, documents each rejection's whole-program
or live-code scope, and retains executable refusal/admission witnesses
(#1170). -
Two host clock builtins,
clock_wall_read()andclock_monotonic_read(),
return(seconds, nanoseconds)as(i64, i64)from a single host reading,
with nanoseconds in[0, 10^9). The wall clock is on the POSIX timescale;
the monotonic clock never runs backwards and has an unspecified origin. Both
carry theIOeffect, so a function declared withoutIOcannot call them.
A host clock error, or a reading whose seconds lie outside
-377705030401..253402214400, fails with<builtin>: io: <detail>.
chelis evalandchelis testrun them through the evaluator's
policy-checked system port, and compiled C runs the same reads
(#1297). [05-OP-75]
inspec/05-risc-primitives.mddefines them. -
Linux releases publish a static build,
chelis-v<ver>-linux-x86_64-static.tar.gz,
and the bootstrapchelisup-linux-x86_64is static too. Both are static-pie
executables that need no program interpreter or system library, so they...
v0.18.12
[0.18.12] - 2026-09-30
Added
-
BREAKING: Reef now parses canonical package identities and prefers valid exact locks.
Package versions use Semantic Versioning without build metadata. Manifest
schema 1 keeps exact dependency semantics; schema 2 activates deterministic
Cargo-style resolver-2 requirements. Noncanonical package names, partial
versions, non-SemVer release tags, and malformed locks, which earlier releases
accepted, now fail closed. See
#1327. -
BREAKING: The
keyelement dtype and the explicit key operationskey_from_seed,split_key,split_keysandfold_in([05-OP-69] through [05-OP-72], derived under [05-RNG-2]) exist in the IR, the DAG evaluator and compiled C, andchelis build --target hiprefuses them with its typed rejection (#2413, step 1).keyis an active tensor element dtype, stored as runtime dtypekey(id 9) in achelis_tensor, with no arithmetic, comparison, cast, literal or default; DLPack and NumPy refuse key tensors. A draw may take a batch of keys, one per row of its data.BREAKING: WireDag adds the four key operations and admits
keyat any rank. -
The compiler API evaluator now routes its seven filesystem builtins and
process_runthrough one injected system boundary with independently checked
Filesystem and Process permissions. Normal evaluation permits both, while
invariant predicate revalidation refuses both before invoking the host
adapter. The default adapter preserves existing filesystem, process, and
error behavior, including byte-ordered directory
names and strict UTF-8 failure under [05-HOST-4]. This evaluator-only change
does not alter compiled binaries or the runtime ABI; compiled host
process_runsupport remains required by [05-HOST-2] and tracked under
chelis#1297. See
#1323. -
Reef documents now have independent versioned schemas. New manifests write
schema 3 and new lockfiles write schema 1. Legacy files remain readable with an
upgrade warning.chelis reef upgrade --check|--inplaceprovides preflighted
ordered migration. Project writers use.reef-write.lockand atomic
single-file replacement. Versioned JSON Schema files underdocs/schemas/reef/
provide advisory editor validation. See
#1327. -
Reef manifest schema 3 accepts typed package metadata: a description, an SPDX
license or a custom license file, HTTPS repository, documentation, and homepage
URLs, and a README. Declared files use portable paths, no-follow Unix opens,
stable bounded snapshots, and deterministic archive membership. Metadata is
not part of logical package identity or resolution, and its fields are not
serialized intoreef.lock,index.json, or.chb. Artifact integrity hashes
still change when declared bytes or the manifest change.
Prepared graph cache version 10 rejects earlier envelopes. See
#1327. -
Reef now performs bounded GitHub release discovery. Normal commands reuse a
valid lock without listing releases.chelis reef update [<package>]performs
a full or targeted refresh, andchelis reef outdated [<package>] [--json]
reports available versions without final writes. Both commands reject index
or remote artifact hashes that disagree with an existing lock, even when a
cache directory or index entry is missing and the package is unrelated to a
targeted refresh. An uncached pin occupies a candidate-manifest slot even
when newer releases exceed the scan limit; an unavailable or mismatched pin
fails closed. Candidate scans, requests, downloads, and resolver states
have finite limits, and Reef publishes complete verified cache entries before
it replacesreef.lock. An explicitchelis-std
dependency uses the compiler bundle without network access. Cyclic path
dependency graphs fail with an ordered cycle report before resolution. See
#1327. -
chelisup installnow checks a release's runtime files before placing it. It
runs the unpackedchelis runtime exportand refuses the release unless the
exported archive and each receipt-listed header are regular non-symlink files
whose SHA-256 matches the receipt, and the shipped archive and headers match
those same digests. A refused release leaves the store untouched. Releases up
to 0.18.11 predate the export and install unchecked, with a warning. A refused
release newer than the running chelisup also says how to get the latest
chelisup, and a release whosechelisthe operating system refuses to execute
is reported as such. See #1354. -
chelis runtime export <dir>writes the runtime archive and public runtime
headers that thechelisbinary carries, with a receipt recording the archive's
SHA-256. Release tarballs and the Nixchelispackage ship its output, so their
lib/libchelis_runtime.ais the archivechelis buildstages. See
#1354. -
A core-fragment eval/C parity receipt runs a pinned corpus of programs through
bothchelis evaland the compiled C lane and compares their complete
observations byte for byte, and their traps by exit status and complete
diagnostic. The corpus is a versioned manifest that records every case's
expected outcome and every excluded file's reason, so a run cannot pass
vacuously. It is a manual gate, documented in
docs/manual_gates.md; its contract is
spec/design/core_fragment_parity_corpus.md.
See #2102. -
chelis reef conform auditrecognizes Coral and Nautilus thin callers of the known historical central workflow revision with closed profile inputs and secret mapping. Structurally parsed YAML jobs and events make alternate indentation, quoted keys (includingon), and aliases obey one authority; duplicate keys fail closed, while comments and run strings remain inert. GitHub owner/repository case differences cannot hide mutable, unknown, or alternate-path central pointers. Each historical CI, nightly, or release profile requires the raw reef compiler, package, and Nautilus sources its actual grep-based jobs read, even when a release caller omits pin inputs; these must agree with parsed TOML and supplied caller pins. The first grep-extractable numeric version on a^nautilusline is decisive, even when an adjacent dependency matches the prefix; later lines do not override it. Valid inline Nautilus trailing fields/comments remain readable, while separate tables alone do not certify the 439 guard. The committed Linux/Darwin toolchain digest lock must match. CI callers must run for main pushes and pull requests to certify the guard and test suites. This audit does not approve a consumer migration, a newer central revision, or hosted execution. -
Add a bounded keyed-randomness acceptance command with exact test receipts and
explicit compiler,parrejection, and shell-release coverage boundaries. -
Downstream shells may add an optional Nix verification job that rebuilds the
shell with atoll'schelis2nixand requires the bytes of the shell's chelisup
build.spec/design/shell_repo_contract.md§2 states its conditions: the
chelisup lane stays the gate, the job runs only on pushes tomain, and it
takes the compiler only by substitution and never builds it.
Changed
-
BREAKING: Randomness now uses explicit keys instead of the
Randomeffect (#2413). Akeyis a non-numeric dtype.key_from_seed(seed)makes a root key;split_key(k)returns two keys,split_keys(k, n)returns atensor[n, key], whose extent followsexpand's rule (a literalnis that literal extent and any otherna runtime extent, checked where it meets another), andfold_in(k, n)derives one key from an integer. The draws take the key first:dropout(k, x, rate)anduniform_like(k, t, low, high); a call at the old keyless arity names the retired spelling. Thewith seed(N) { ... }handler, theRandomeffect name in Surf effect annotations, and therandomkind of Deep'shandle-effectare removed; each is now a typed rejection that points at keys. A function that draws takes akeyparameter instead.Keys are affine ([04-LIN-9]): each is used at most once on every path and cannot be copied, borrowed or captured by a closure, and reuse is a
KeyReuseerror suggestingsplit_keyorsplit_keys. An as-pattern cannot bind a key-carrying value together with a key-carrying component of it, and a key a match guard consumes stays consumed in every later arm, because a failed guard falls through.filter,partition,scanandtensor_scanrefuse a key-carrying element or accumulator, which would reach both their callback and their result, whilemap,foldand the other list builtins move keys; tensorconcatandsplitrefuse key tensors; andvmaprefuses a key-carrying argument it would share across rows. A key-carrying value reaches only the operations the key allow-list names: a key derivation or draw at its key operand,drop, a branch's join, construction and destructuring, a move, a list builtin that routes each value to one consumer, and a call through a key-typed parameter,grad,vmapandjitapplications included; reading a key tensor's extent withshapeornumel(as anexpand,insertorreshapesize too), or at an extent check (of a call whose signature shares a dimension between a key tensor and other...
v0.18.11
[0.18.11] - 2026-09-21
Added
-
HIP now executes direct tensor comparisons, Bool8 logical operations, and
stored-bitwhereselection across all active device dtypes. Metal reports a
stable typed unsupported diagnostic for these operations until its exact
kernels land. See #1284. -
The
diagonalandtraceaxis rules now have expectations that neither runtime
computes. A new CLI suite derives extents and elements from [05-OP-33] and
requires the IR evaluator lane and the compiled C lane to agree with those
expectations separately, over all six ordered rank-3 axis pairs, both rank-2
orders on a non-square matrix, and a signed source. The parity harness header
now states what lane agreement does and does not establish. See
#1351. -
The Deep canonical form specification states the order of annotation metadata map entries. Entries are ascending by key spelling under ASCII byte comparison, and producer-specific and
span_*extension keys take their places in that one sequence beside the defined keys. The order was already observable in canonical Deep output and was the one member missing from the chapter's ordering enumeration. See #2214. -
python3 scripts/gate.py --fastnow classifies the changed path set through
the change-owned planner's own rule lookup and refuses a push that adds a file
no[[path_rule]]in.config/ci-test-targets.tomlroutes. The planner
itself cannot run locally, because inpull_requestmode it requires a
two-parent synthetic merge, so until now a new tracked file passed every local
check and then failedPlan Changed Integration Testsin CI. The check prints
the sameunclassified changed path: <path>sentence CI prints, from one
shared spelling, and reports every unrouted path rather than stopping at the
first as CI does.It derives its own changed set when it runs, so a modified artifact is
classified in the run that changed it. A path is classified once git knows
about it, so a file a writer has just created is seen at the next--fast
aftergit addrather than in the run that created it; an unadded file is
not part of the change and cannot be pushed. It matches the
planner's rename handling, classifying both sides of a move rather than only
the destination, and it ignores untracked work, which CI never sees and
nothing can route. It is not a proof that CI will agree: it reads the working
tree, while CI composes base and candidate Cargo metadata inside the synthetic
merge, so a change to the package set itself can be ambiguous to the planner
and clean locally.Four paths that no rule routed are now routed, three of them generated
artifacts whose only consumer runs nightly.ci_change_owned.py classify-pathsis available on its own, with--from-gitfor the derived
set. -
Downstream shells can omit an entire shared agent skill with
conform.excluded_skills, or remove exact inherited sections from a retained
skill withshell-local:excludeheading selectors. Sync and version bumps
reapply both forms of exclusion while preserving shell-specific additions. -
Add a repeatable PR lifecycle report that inventories every attributable
GitHub Actions run and attempt, estimates per-job-rounded Linux list price by
runner SKU, rejects stale or identity-rewriting attribution, and separates
latest-candidate validation, cumulative execution, semantic causes, and
temporally bound trace-attributed agent waiting.
Changed
-
BREAKING: Chelis source and canonical Deep now spell signed integer dtypes
i8,i16,
i32, andi64. The retiredint8,int16,int32, andint64language
spellings are rejected with migration guidance. Run
chelis migrate surf --from 0.18 --inplacefor.chsource and
chelis migrate deep --from 0.18 --inplacefor.dpsource. Existing
compiler-API JSON, WireDag, cache, and C ABI dtype names remain unchanged.
See #1592. -
BREAKING:
chelis_types::Schemegains a publicconstraints: Vec<CollectionConstraint>field, so direct struct literals must supply it;Scheme::monois unchanged.TYPE_ENV_FORMAT_VERSIONadvances from #2071's version 2 to version 3. Older checker snapshots are rejected instead of silently restoring checked collection function values without their contracts. See #1654. -
BREAKING: Published package metadata now records checked collection relations.
chelis_shell::ShellSymbolgainscollection_obligations: Vec<CollectionObligation>, CHB advances from #2071's format 4 to format 5, andreef schemaadvances from format 2 to format 3. Each relation nameslen,index,append, orconcatand carries exact canonical Deep operand/result types under variables already present in the exported callable type; ledgers are strictly ordered and unique, and hidden variables are rejected. CHB validation and encoding/decoding plus public Reef-schema deserialization reject parse-equivalent noncanonical strings, duplicate or out-of-order rows, predecessor versions, and unknown versions. CHB and Reef schema protect publication identity; serialized TypeEnv owns compiler checker reuse. See #1654. -
BREAKING: Function signatures now quantify type, dimension, and rank variables only
from explicit[...]binder lists. Add every formerly implicit name to the
owningsigordef; a standalonesigowns the declaration's sole list.
Canonical polymorphic Deep inserts a structural(binders...)child between
thedefsigname and type, while monomorphicdefsigremains two-child.
Binder lists reject active, reserved, retired, and deferred dtype spellings
even when unused or used as dimension/rank variables. Unknown dtype-like
names are rejected with the nearest active spelling and one diagnostic per
declaration/name. -
BREAKING: The List slice
drop(xs, n)is nowskip(xs, n).dropkeeps only its
one-argument form, the linearity consume that pairs withcopy(). The two were
one builtin declaration separated by argument count, so a dropped argument
turned one operation into the other and still type-checked; they are now two
declarations governed by two atoms,[05-OP-54]for the slice and a new
[05-OP-67]for the consume.skippairs withtake, which already had the
matching semantics.Migrate existing sources with
chelis migrate surf --from 0.18 --inplace. The
rewrite is arity-driven and scope-aware: it renamesdrop(xs, n)and the pipe
stagexs |> drop(n), and leavesdrop(value),xs |> drop, and anydrop
shadowed by a parameter, lambda parameter, block binding, or match binder
untouched.Two further consequences for existing code.
skipjoins the closed builtin
vocabulary, so a top-leveldef skipis now rejected asBuiltinShadowing
(spec/04 section 8.6); rename that definition or scope it inside a Reef
package. The standard library'sStd.Indexwrapperdrop_listis now
skip_list, with the same signature and semantics.chelis lintgains the advisory rulerecursive-list-cursor
(spec/01 section 12.3). It reports a self-recursive definition whose recursive
call passesskip(p, k)in the argument position its own parameterp
occupies: that walk copies the List once per step and costs time quadratic in
its length.Std.Tokenizer's three JSON entry builders had exactly that shape
and now run onfoldandmap. -
chelis lint's advisoryrecursive-list-cursornow also reports a cursor the
recursive call reaches through a local binding, as inrest = skip(xs, 1i64)
followed bywalk(rest, ...). Previously it read only askipwritten in the
argument position itself, which missed every cursor found in the shell
ecosystem. Substitution is one level deep and respects binding order, and a
name a definition binds more than once is still never substituted. A cursor
reaching the argument through a call to another function remains outside the
rule permanently: whether that call returns a suffix of its argument is
interprocedural.spec/01-nomenclature.md§12.3 states the new scope. See
#2328. -
chelis buildno longer formats an ownership-verification diagnostic that the
success path discards, and no longer desugars a single-file program twice. The
first helps every build; the second helps only a build outside a reef package,
because a package build's second pass already covers just the entry module. The
emitted artifacts are unchanged, and the live-owner overflow diagnostic is
byte-identical on the path that raises it. See
#2331. -
skip(xs, n)on a List the compiler proved is at its last use now advances an offset inside the existing allocation instead of cloning the retained suffix. A recursive cursor that binds its head before recursing therefore runs in linear rather than quadratic time; one that reads the head inside a later argument of the same call keeps its operand live and stays on the cloning path, as does a List any other owner still holds. -
Pull request CI now publishes default-branch-verified candidate receipts that
bind the exact head, synthetic merge, patch identity, and required-check
provenance. These receipts provide trusted input for later evidence reuse;
pull-request code cannot issue or validate its own receipt. -
Change-owned pull-request tests are now assigned to four shards using reviewed target-duration evidence, preventing a hash collision of slow targets from overloading one runner without increasing the job count or changing test ...
v0.18.10
[0.18.10] - 2026-09-15
Changed
-
BREAKING: Generic function bodies must satisfy their declared dtype-family bounds: an
unbounded orNumericbinder cannot use a float-only operation without a
sufficientFloatcontract. Omitting a signature does not publish an inferred
generic admission contract; local inference holes must bind within their
enclosing declaration or be justified by its declared bounds. Checked
operation-family restrictions now survive function values and transitive
calls. The specializedreduce_window_*shape path consumes those same
contracts:reduce_window_meanrequiresFloat, while window sum, max and min
requireNumeric. Older checked-context caches and package shells must be
rebuilt. -
Move broad PR package expansion to an exact-head manual dispatch and validate
PR contract acknowledgements without restarting compiler CI. Base retargets
hold a required exact-head/exact-base implementation receipt. Directly changed
all-ignored integration targets can now opt into required complete ignored-suite
execution with exact per-test receipts. Empty integration shards skip build
setup, exact standing receipts prevent duplicate execution, and manual
expansion batches ordinary targets by package without losing per-test identity. -
The release workflow extracts the tagged release notes in a preflight job
before any platform build starts, so a tag whose commit still carries
changelog.d/fragments fails within a minute instead of after the builds.
Previously the check ran only in the publish job, and the v0.18.8 tag failed
there about 37 minutes in. See
#2058.
Fixed
-
Preserve literal result claims and producing-operation attribution across pure tensor helper calls.
-
Check host literal result extents in the selected
iformatchbranch and carry callers' claims into shared host callees, preserving primitive attribution and suppressing effects after a failing producer on eval and compiled C. -
Host signature extent checks cover every declared tensor parameter before the body, even when tensor helpers own only part of the signature. Literal dimensions and repeated binders retain parameter/axis order, and supported higher-order and inline callback invocations preserve their entry checks in C. Private helper schedules avoid repeating checks already discharged at the invocation (#1788).
-
Preserve authored named-extent claims when evaluating gradients of tensor, aggregate and primitive scalar targets, including multiple targets and unused zero cotangents. A rejected forward activation reports its required extent failure instead of producing a gradient.
Independent calls retain distinct extent claims, including computed result extents. Their guards keep the original producer attribution and preserve the claimed value until the guard executes in generated C.
-
Keep final PR package-expansion reporting on the exact candidate schema while
retaining trusted stale-head, base, and synthetic-merge validation. -
Compiled C gradients accept primitive scalar targets alongside tensor and
aggregate targets, retain forward extent failures even for zero cotangents,
and reconstruct complete results in written target order, including repeated
selectors. Primitive scalars and rank-zero tensor results keep their distinct
public types. Fixes #1934. -
The wire census validates diagnostics with internal rejection metadata without treating that metadata as serialized numeric data, fixing the missing
NonZeroU32artifact failure tracked in #2048. -
The interpreter classifies a closure body's execution profile against the
program's top-level definitions on every application. It now builds that sorted
definition snapshot once per evaluation and reuses it, instead of re-cloning
every definition on each call. Achelis testrun over a package with many
definitions and a fold-heavy test is back to its former speed, dropping from
tens of seconds to about one, matching 0.18.6. See
#2059. -
The native C backend no longer rejects a by-value scalar that a tail-position
user-function call uses in more than one argument slot. Ownership lowering
moved the single owner on the first slot and then read it dead on the second,
failing withowner %N is not live(for exampleg(x) = f3(x, x)). A Copy
scalar owns no resource, so each slot now duplicates it and the original stays
live; heap values still move on their last use, so a genuine use-after-move of
an owned resource stays rejected. See
#2068. -
Preserve completed test evidence when optional CI package expansion exhausts its execution budget, while reporting unfinished coverage as unsuccessful.
v0.18.9
[0.18.9] - 2026-09-14
Added
-
CI publishes a merge-base report of changed Phase 3 required-test definitions and membership alongside the existing definition digests and behavioral oracle.
-
CI publishes changed normative atoms, their registered text, and contract regions beside the existing Phase 4B freeze checks.
Changed
-
Replace protected atom and contract-region checksum updates with required identities, preserved semantic checks and exact PR change acknowledgements.
-
Replace protected-test definition checksums with required change acknowledgements and parsed comparison/result checks, preserving coverage and behavioral controls.
-
Derive parity-corpus membership from Rust test inputs and executable examples, with standing controls for missing or hidden inputs instead of a copied filename list.
-
Phase 4B pull-request validation now requires exact atom and region acknowledgements alongside changed-file acknowledgements, including registry ownership and removed protection boundaries. Existing digests and semantic controls remain active.
-
Document the validation required to retire the temporary Nix workflow digest when its event policy is replaced.
-
Require explicit PR-description acknowledgement of each changed protected test, preserving its definition and behavioral guards.
-
Protected comparison tests now have a standing syntax check for their required calls and executable modes, alongside the existing definition freezes.
-
Document the guard-inventory rule: derive tree membership, retain reviewed dispositions and coverage floors, and never generate semantic authority from a census.
-
Add an AGENTS-linked PR-author guide for test coverage, inventories, protected-contract acknowledgements, rejection messages and the temporary Nix workflow freeze.
Fixed
-
A tensor whose dtype is a declared type parameter can flow through an
if/then/else. Previously any such program aborted the compiler with an internalBUG: monomorphization missed precision varmessage and exit 101 instead of returning a verdict, so a dtype-generic definition containing a conditional could not be checked, built, or run at all. The abort came from a routing predicate that asked whether anifcould be lowered to the float DAG and read the node's precision through a reader that panicked on an unresolved binder; an unresolved dtype simply means the predicate cannot establish a float DAG join, so it now answers "no" and the item takes the general host lane. Generic definitions monomorphize at their concrete call sites as before, including under theFloatandIntdtype-family bounds ofspec/04-type-system.md§5.9, and the§5.8.1backend assertion is unchanged on the emission path where it belongs (chelis#1541). -
Restore each integration worker's current test plan after its Cargo cache, so cache replacement cannot remove or overwrite the plan before execution.
-
Phase 3 guard and change-report discovery reject commented-out required tests; PR reports use the validated synthetic merge's actual base, and both Phase 3 and Phase 4B reports select push mode only for push events.
-
Capacity-census liveness now reads final native binding contracts and rejects their retired legacy citations.
-
Typed unsupported lowering rejections now report
unsupported_featurethrough the compiler API, preserving their stage, message and source association; ordinary lowering errors retainlower_error. -
Host gradients whose original operand directly names a checked function
declaration, without a captured binding for that target name, now read free
tensor values from the declaration environment instead of caller-local shadows.
Required captures reuse successful initialization and preserve entered errors;
optional shape queries do not initialize absent declarations. Argument order
and anonymous creation-time captures are unchanged. This does not extend alias
or captured-target admission, general gradient support, or backend coverage. -
Selected Host evaluation entries now receive their required caller-supplied tensor
arguments, including inputs used only by lowered shape witnesses. Missing inputs
leave the declaration unentered, while dead and unrelated bindings remain
unobserved. Prepared library calls preserve fresh actuals, existing Random
behavior, and runtime error ordering. Fixes
#2013.
v0.18.7
[0.18.7] - 2026-09-13
Added
-
Add an internal compiler evidence mode for auditing native Python bindings, recording exact constructor owners, instantiated types, calls and control flow.
-
Native binding compiler evidence now records exact tuple-struct and enum constructor function exposures, including callback arguments, stored values, promoted static tables, reification casts and constant initializers. Its versioned decoder requires the exact occurrence envelope and rejects older incomplete native evidence; collection alone grants no binding authority.
-
chelis build --target hipand--target metallower first-classcount
([05-OP-29]) to a dedicated device kernel over the exactBool8carrier, and a
host program whose tensor helper containscountemits that helper as its own
device translation unit instead of a C body. Both targets previously rejected
countwith a chelis#1291 receipt. The Metal hardware gate records a pass on
Apple Silicon; the HIP hardware gate remains a documented manual gate, so
chelis#1291 stays open. See
#1307. -
The opt-in
chelis-ir/lowering-tracefeature records gradient call-site
specialization, argument and splice maps, caller snapshots, and packed results
for downstream validation tooling. The feature remains disabled by default and
does not change ordinary compiler output or language behavior. -
OpenSpec document changes land by pushing a branch. A push touching
openspec/**startsopenspec-autoland, which classifies the pushed commit
with default-branch code, opens an internal pull request when every changed
path is an OpenSpec document, waits for the required checks on that exact
commit, and merges it. Normative capability specifications are included.
Anything outside the document set is refused and follows the ordinary review
path. Inside Devenv,openspec-submitremains an optional local helper that
validates before you push. See
#1653. -
Add IR evaluator APIs for preparing strict-root and selected-plan inputs through
a fallible provider before execution. Existing evaluator signatures and input
selection remain unchanged. This is a prerequisite for, not a fix to,
#1956. -
chelis prove --tier beacon-onlyand the Tide prove tool can dispatch self-contained scalar network bounds to Beacon, retaining real-arithmetic qualification, search bounds, unknown reasons and split trees. Search and caller wall budgets are explicit; float execution is not covered. -
Add tested rustdoc graph and structural carrier-verification infrastructure for
numeric wire-surface discovery, including private/imported helpers, generic
closure and fail-closed unsupported codec handling. Live wire formats and census
activation remain part of the subsequent migration. -
Added a fail-closed internal adapter for the future #1288 zero-exception capacity receipt. It cannot issue a receipt until the primary, stdlib, wire, and final PyO3 binding gates run on one committed source tree, and retains each nested Cargo/libtest execution's framework outputs beside its supplied fresh receipt. The required stdlib selection includes symbolic generic constructors and imported boolean companions.
-
Release notes can be authored as per-PR Markdown fragments, assembled into a
versioned changelog section, and published verbatim with the GitHub Release.
Required PR checks reject missing or invalid fragments and direct changelog edits
outside release assembly. See
#1251 and the
fragment contract. -
Add the default-off
chelis-compiler-api/emission-observerfeature for immutable
observations of the actual ownership-verified payload selected for code generation.
Ordinary compiler output and behavior are unchanged; observations are not certificates. -
Add an opt-in ownership-checked C backend entry for closed fixed-control dropout execution plans, with saved-mask replay and unchanged tensor ABI. Ordinary source-build and host-helper admission remain separate work.
-
The default-off native Random observer now carries bounded, ownership-verified
host source-site and linked call associations, with exact helper-local/full
source IDs. Unsupported source shapes remain explicitly uncertified; ordinary
generated headers, public entry ABIs and default emission are unchanged. -
Add a non-default compiler feature for translation-unit-private observation of
actual generated-C Random state, including nested saved frames, fixed-control
forward/replay identities, and copied continuation counters. Public headers,
ordinary artifacts, runtime behavior, and default features remain unchanged. -
The opt-in native emission observer now exposes the initial host-lowering snapshot
alongside the selected verified payload, so consumers can inspect scalar losses
pruned from tuple-gradient artifacts. Ordinary compilation and wire output are
unchanged; the additional snapshot is not a certificate or an emitted function. -
Add the pre-Phase-4C prerequisite runner framework with exact child-command
identities and execution-receipt validation. It fails when prerequisites,
per-test outcomes, or required negative and mutation evidence are absent.
The complete prerequisite set and required CI activation remain outstanding
under #1296. -
C fixed-control lowering retains checked Resource requirements as value-free
source cuts, exposed through opt-in full-spine observations. The existing target
validator still decides compatibility. Resource-only programs and the evaluator
keep their prior lanes; existing random-only Rust carriers, wire formats, C ABI,
and shell interfaces are unchanged. -
Add an opt-in compiler API that pairs actual selected helper lowering/AD observations with the final successful C artifact. Normal compilation, existing observer layouts and shell/wire interfaces are unchanged; the observations are not correctness certificates.
-
Retain draw-free seed controls and an independent source occurrence census in
fixed-control evaluation plans, and expose opt-in execution-bearing snapshots
at actual AD calls. Compiled backend support and public wire formats are unchanged.
Changed
-
BREAKING: Compiler execution JSON advances to version 3 and WireDag JSON to version 9.
Scalar and tensor carriers preserve exact integers and IEEE floating-point bits,
including signed zero and NaN payloads. Numeric parameters, extents, references
and report fields enforce their owning domains on encoding and decoding.
Readers reject missing, older and future versions; regenerate saved wire
documents with the current compiler. The wire census now requires executed
final authority for every discovered numeric leaf and retains no legacy cohort. -
BREAKING: Python evaluation uses execution wire v3's exact numeric carriers. Floating-point
values preserve their IEEE bits, including signed zero and NaN payloads; numeric
scalar results retain their NumPy dtype, withml_dtypes.bfloat16forbf16.
Tensor results retain the declared dtype and exact element values. Old execution
versions and malformed carriers are rejected. Part of
#1288. -
BREAKING: Compiled-context worker handoffs use the same versioned, integrity-checked
envelope as the disk cache. Workers reject unversioned payloads, incompatible
formats or compiler builds, and corrupted payloads before reconstructing the
checked context. Regenerate previously saved worker handoffs with the current
compiler. -
BREAKING: The reserved
normalizebuiltin is removed. An undeclared call reports an
unbound variable; define an ordinary function with an explicit normalization
formula, as inexamples/explicit_normalization.ch.Builtin and canonical RISC semantic identities now have an exhaustive normative
atom registry and an executable closure oracle. See
#1299. -
BREAKING: Deep AST annotations use dedicated types for compiler-owned metadata and a
separate extension map. Rust API consumers must migrate fromMetaMap.entries
toMetadataand its typed accessors. Parsing, deserialization, and typed
construction reject malformed annotation values and duplicate keys, including
custom andspan_*keys; preserved macro-source arguments remain raw syntax
data. See #1604. -
BREAKING:
chelis check <dir>emits one typed envelope,{"files": [...], "errors": [...]}, with both members always present, and it never exits 1. Three behaviours change for a directory target. An empty directory now fails with anempty_corpuserror and exit 2; it used to pass. That includes a directory whose only sources sit undertarget/or a dot-directory, and the message counts those. A directory the walk cannot read no longer discards the whole run. Every readable file keeps its report, the unreadable directory is onedirectory_walk_error, and the command exits 2; it used to exit 1 with nothing on stdout. A populated directory's document now carrieserrors, which it used to omit.The walk shared by
chelis check <dir>andchelis test <dir>also changes. Each file and directory is visited once, by canonical path, so a file reached through several links is checked or run once. A dot-named ortargetlink is skipped by its name before it is resolved, so it no longer aborts the walk when its target cannot be read. A link that cannot be resolved is still an entry when it has a source name. Forcheck, any other unresolvable link is a `...
v0.18.6
What's Changed
- Name the newline, not the semicolon, in the v0.19 block separator diagnostic by @rlronan in #1272
- (#730 class): report an abandoned test batch and keep a sibling def out of an explicit import by @rlronan in #1273
- Resolve host-lowering constructor references on their full mangled name (#1271) by @rlronan in #1275
- (#788 class): fix the registry dep graph, preflight the conform write path, widen §4 citations, settle skill uniformity by @rlronan in #1279
- fix(scripts): regenerate the compile-fail fixture locks on a version bump (chelis#1128, chelis#1234) by @glampouras in #1233
- B2 (#908 unrepresentable AST): one stamped Deep ingress for the compiler API (chelis#1088) by @rlronan in #1285
- Make DeepTag realizability classification exhaustive by @rlronan in #1301
- docs(types): correct #1207 evidence and level-generalization plan by @vrk210 in #1227
- Specify grounded dtype capabilities and Phase 4 delivery by @rlronan in #1283
- (#729 Phase 4B follow-up): relu adjoint, stop_gradient barrier, JsonBigInt ingestion, gap receipts, exact matmul/einsum by @rlronan in #1317
- Consume target-aware root manifests across eval, C, and HIP by @rlronan in #1310
- Replace environment-wide type generalization sweeps with solver levels by @rlronan in #1318
- B4 (#908 unrepresentable AST): stamping fidelity — [03-ROLE-1..3], variant dispositions, hygiene recursion (chelis#885, chelis#1087) by @rlronan in #1319
- Make function inference SCC planning linear by @rlronan in #1328
- Hand the gate's built chelis to the #908 oracle and lock the build-before-oracle ordering by @rlronan in #1325
- Correct trace rank-zero test contracts (chelis#1148) by @rlronan in #1326
- Implement first-class bool tensor count and WireDag v6 by @rlronan in #1303
- Materialize proven zero gradient roots by @rlronan in #1331
- Make nested front-end lowering linear by @rlronan in #1332
- docs: codify agent workflow and repository operating rules by @rlronan in #1335
- docs: generalize agent ownership boundaries by @rlronan in #1337
- Implement final capacity-authority foundation by @rlronan in #1336
- fix: diagonal/trace retained-axis index mapping in both lanes (#1349 memory safety) by @rlronan in #1353
- #1286 (compiled value ownership): free each root allocation exactly once in the C host emitter (#1222) by @rlronan in #1340
- Replace the public C numeric ABI with exact tagged carriers by @rlronan in #1308
- Align the exported stdlib numeric surface with [05-OP-35] by @rlronan in #1302
- docs: define hash-order determinism plan by @rlronan in #1366
- Phase 1 (#730 loud unsupported): stop dispatching four-byte HIP kernels over one-byte bool buffers by @rlronan in #1365
- Implement direct checked subtraction and exact extrema selection by @rlronan in #1309
- Make tensor closeness use each float dtype's arithmetic width by @rlronan in #1304
- spec+docs: decide runtime extent rules and trim the #1277 plan by @rlronan in #1343
- docs: scope red-team findings to pull request by @rlronan in #1381
- docs: amend hash-order plan to a type-level mechanism and decide settlement order by @rlronan in #1370
- release: prepare Chelis 0.18.6 by @rlronan in #1385
Full Changelog: v0.18.5...v0.18.6
v0.18.5
[0.18.5] — 2026-08-22
Three breaking entries, all in the front end: the checker rejects
polymorphic recursion, the parser rejects an integer literal in a bare
type position, and > evaluates its operands in the order they were
written. Each one either rejects a program that used to be accepted or
runs an accepted program differently; each entry names what to change.
Every on-disk compiler cache is invalidated, twice over. The
compiled-context, stdlib, and library cache formats advance (8 → 9,
4 → 5, 1 → 2), and cache keys now carry a build fingerprint rather than
the release version. Both are automatic: the first build after
upgrading is cold, and nothing needs to be deleted by hand.
Added
- Recursive generic host calls compile via bounded memoized
monomorphization (chelis#1158, successor to chelis#941). The C-emitting
host lane now compiles a recursive (direct or mutual) generic host call by
emitting one specialized definition per distinct checked type application,
memoized so each(function, instantiation)pair is generated exactly
once, with recursive edges lowered as ordinary calls to the owning
specialized symbol (<def>__mono_<hash>, deterministic across builds).
The former brandedrequires bounded monomorphized symbolsrejection is
retired for these programs; a surviving generic call whose checked type
application never resolves still fails closed with a brandedunsupported:
diagnostic citing chelis#1158, and no C artifact is written. HIP and Metal
targets consume the same shared host lowering. Acceptance oracle:
cargo nextest run -p chelis-cli --test recursive_generic_monomorphization --no-fail-fast.
Changed
-
BREAKING (checker): polymorphic recursion is now a check-time type
error ([04-INF-2]/[04-INF-3], spec/04-type-system.md §3.1.1). Every
recursive call inside a recursive binding group must be typed at the
caller's own instantiation; a call whose type application embeds a type
variable inside a larger constructed type is rejected by the checker with
a diagnostic naming the function, both instantiations, and the deciding
atom — identically across the eval and build lanes. Previously the eval
lane accepted and executed such programs (they never compiled natively);
the reproducer below now rejects atcheck:type Box[a] = | Empty | Full { value: a } def f[a](x: a, n: int32) -> int32 = if n <= 0 then 0 else f(Full { value: x }, n - 1) + 1 def main() -> int32 = f(1, 3)Fully concrete (type-variable-free) recursive type arguments remain
admitted for signature variables introduced by inference rather than
authored[a]binders, so partially annotated recursive defs keep
checking exactly as before. -
BREAKING (parser): an integer literal in a bare type position is now
a parse error (chelis#1179). The Rust Surf parser shared one integer
arm between ordinary type atoms and dimension items, so an integer
parsed as a type anywhere a type was expected and desugared to a fresh
type variable —def g(a: 732) -> f32 = aproduced(t-var {} 732)
andchelis checkscored it 1.0. The Tree-sitter grammar already
rejected the spelling, so the two parsers disagreed. Both now agree: a
bare type position rejects with "expected a type; integer literals are
only dimensions insidetensor[...]" naming the byte offset, in the
canonical and legacy v0.18 parse modes alike. The two dimension
positions keep accepting integers: tensor shape items
(tensor[3, 4, f32]) and type-application arguments, whose intended
meaning is the concrete dimension instantiation of a
dimension-parameterized ADT (Frame[2], chelis#940). The spec grammar
gains the previously missingTypeArg <- TypeExpr / IntLit
production, and the Tree-sitterapplied_typerule matches it, so
Rust/Tree-sitter parity holds on both sides of the split. This is the
parser half only: the checker does not yet enforce an integer
argument's kind or extent (Option[732]still checks, and a
Column[3]return holding a 2-element column still scores 1.0);
chelis#1247 tracks that surviving mechanism. -
BREAKING (eval/build):
>evaluates its operands in authored order
(chelis#1180). Surf desugareda > bas the operand-swapped
cmplt(b, a), and Deep application evaluates its arguments left to
right, so the right operand's effects and traps ran before the left
operand's in every executable lane:verdict = lhs() > rhs() -- printed RHS then LHS, in eval and compiled C>now desugars to the existinggtbuilt-in with the authored
operand order. Comparison results are unchanged, includingfalseon
either NaN operand, becausegt's value is defined ascmplt(b, a)
over the already-evaluated operand values
(spec/05-risc-primitives.md§3.2). Effectful operands now run left
to right in both lanes with byte-identical stdout, and when both
operands trap, the left operand's trap surfaces; a program that
observed the reversed order changes behavior. One further consequence
of the retarget:gtborrows both arguments wherecmpltconsumed
its second, so>'s operands stay usable afterward
(mask = a > bthenwhere(mask, a, b)now checks) while the
equivalentb < aspelling still consumes — a strictly monotone
loosening; the family-wide inconsistency is tracked at chelis#1248.
The general operand evaluation-order rule is now normative at
spec/03-deep-syntax.md§4.4, and the Deep consumers that matched the
closed comparison-name set (the D-WF invariant grammar, its
boolean-shape check, constant folding, and the Tier B / opaque SMT
lowerings) admit thegtspelling, so@invariant(p) p.value > 0.0
keeps working end to end. -
Compiled-context caches are keyed on the build, not the release
string (chelis#1156).CacheIdentityandstdlib_cache_keyfolded in
COMPILER_VERSION, which names a release: every binary built from any
commit carrying the sameworkspace.package.versionreports it, so two
binaries that disagree about type semantics shared cache entries and
checked programs under each other's rules. Measured on a released
v0.18.2binary and a post-chelis#1130mainbinary — both reporting
0.18.2— sharing one cache dir: the release binary accepted and
evaluated a program it correctly rejects cold, and themainbinary
raised a spuriousprecision mismatch: expected int32, got int64on a
program it correctly accepts cold. Both keys now extend the release
string with a build fingerprint taken from the running object image
(Mach-OLC_UUID/ ELFNT_GNU_BUILD_ID, or a whole-image SHA-256 when
the image carries neither), derived by the new leaf crate
chelis-image-id. A same-content copy at a different path still shares
the cache; a different build takes a clean miss rather than a stale hit;
a version bump alone still flips the key. If the running image cannot be
identified at all, the fingerprint falls back to a per-process
discriminator and warns once, so a degraded process shares with nothing.
This mostly bites people who build from source — worktree binaries, CI
matrices, and images that vendor a pinned commit and label it with the
workspace version. -
Runtime diagnostics name a value by its dtype and canonical digits
(chelis#997). The JSON, CSV, and eval runtime diagnostics rendered
numeric payloads through derivedDebug, whichspec/05-risc-primitives.md
[05-OBS-1] already forbids: a diagnostic is one of the exits that must
"emit text that parses back to exactly the stored bits at the value's own
dtype width". Reported text changes shape —got f32 0.1and
got int64 9007199254740993in place of the Rust representation, a
malformed ADT field list asmalformed JNum fields [int32 5], an absent
argument asgot nothing— and below the top-level dtype tag the
diagnostic channel now agrees byte for byte with the exit channel. The
half widths were misreported outright before this:half::f16'sDebug
forwards toto_f32(), so a stored f16 that every exit renders as0.1
appeared in a diagnostic as0.099975586. Anything that scrapes runtime
diagnostic text needs updating; nothing about the values themselves
changed. -
Compiled-lane CSV ingestion is linear (part of chelis#943). Reading a
9588-row by 8-column, 420 KB CSV in the compiled lane allocated 3055.6 MB
and held 2189.9 MB live; it now allocates 121.0 MB and holds 91.4 MB, a
25x reduction in allocated bytes.Std.Io.Csv.parse_rowsmoves off
recursion ontomap/fold(one line carried two independent O(n²) costs:
appenddeep-cloned the accumulated rows per row, anddrop(lines, 1)
cloned the remaining tail per row),chelis_string_slicelocates byte
offsets withchar_indices()and takes an O(1) all-ASCII fast path
instead of materializing aVec<char>per call,chelis_string_len
returns a cached count instead of recounting, andchelis_list_append
reserveslen()+1so the following push does not realloc. Character
indexing and multi-byte behavior are unchanged, andtry_read_csvstill
returnsNoneon a ragged row or an unterminated quoted field. Honest
cost: eachRuntimeStringgrows by exactly 8 bytes for the cached
count, so a workload that builds many strings and never slices them or
takes their length is a net allocation regression. Not fixed here: JSON's
parse_array_rest/parse_object_restare still quadratic, and a
user-written recursiveappendaccumulator is still quadratic —
chelis#943 remains open for both. -
The Rust toolchain is pinned to 1.98.0 (PR #1236; chelis#1237 for the
Nix half).rust-toolchain.tomltracked thestablechannel, so a Rust
release reached every branch at once with no staging; 1.98.0 (released
2026-08-20) tightened two lints and turned the wh...