Releases: vyncint/launchbound
Release list
launchbound 2.2.0
The cuda-oxide pin was 133 commits and one toolchain behind, and the watch
that would have said so had its schedule commented out. 2.2.0 catches up,
turns the watch back on, widens the device model past Ampere, and closes the
panic and arithmetic gaps user input could reach — plus the semver and
documentation gates a published 2.x line should have had from the start.
Ten issues (#50–#59). Four of them were wrong when filed, and measuring
before implementing caught each one; the corrections are recorded on the
issues and repeated below, because an audit that miscounts is worth less
than no audit and the shape of the miscount is the useful part.
Changed
-
The lockstep pins move to the current upstream, at every recorded
site:nightly-2026-04-03→nightly-2026-08-28, cuda-oxide
50d07314→26754ae5(upstreammainon the day), reconverge
0.5.0→0.6.0. The prune leg never needed this — it is analysis
only, which is why the staleness stayed invisible — but the compile leg
shells out tocargo oxide inspectfrom the checkout the pin names, so a
kernel written against a currentcuda-devicecould not be built by the
gate at all.Verified rather than assumed: the corpus ran under both pin sets and the
outputs were diffed byte for byte — 93 clean, 8 refused, the same
eightREFUSED RC001lines onreduce-flipabove one warp, the same
candidate hashes. All six corpus kernels lower to PTX at the new pin
(.target sm_80, one.visible .entryeach). Recorded in
docs/research-baseline.mdin the shape the 0.1.11 → 0.3.0 comparison
established. (#50) -
The README toolchain badge is a checked pin site. It was not one, and
it had drifted to a nightly nothing installed — the first pin a reader
sees.check-pins.shreports three nightly sites now and fails on badge
drift, which was verified by drifting it on purpose. (#50) -
The device model reaches Blackwell.
DEVICESheld two rows, A10G
(8.6) and T4 (7.5), so--cc 9.0on a Hopper part — the architecture a
reader arriving from cuda-oxide is most likely to be holding — was a model
error. Added 8.0 (A100), 8.9 (L4/L40), 9.0 (H100) and 10.0 (B200).Every field but
sm_countis a compute-capability fact from the CUDA C++
Programming Guide;sm_countis a product fact and each row names the
part it came from. The new rows' shared-memory figures were cross-checked
against reconverge 0.6.0's independentcc.rstable and agree exactly,
allowing for the 1 KiB the driver reserves per SM on Ampere and later. An
unknown capability now lists the known ones, sorted numerically —"10.0"
precedes"8.6"as a string, which is the trap. (#52) -
launchbound_bench::Summaryis re-exported fromlaunchbound-report.
It appears in the public fields ofCandidateReport,ChosenInfoand
RejectedFaster, so it was reachable and unnameable unless you also
depended on the bench crate. (#55) -
launchbound-spacerefuses an impossible block at load. A block axis
above CUDA's per-axis limit (x, y: 1024; z: 64) is now rejected by
KernelSpecwith a message naming the value and the limit, so an operator
hears about a typo when the spec loads rather than as a launch failure ten
minutes into a sweep. (#53) -
The PTY test harness moves to termlens 0.10.1 (from 0.9). The upgrade
itself is small —dragtakes four column-first arguments now instead of
two coordinate pairs — but it brings the accessor this suite was missing. -
tests/emulation.rspins what the emulator drops. Every screen
assertion in this crate reads a grid a VT emulator produced from the
binary's bytes: five golden files, two border scans and a styled banner. A
sequence that emulator does not implement makes the grid quietly wrong and
every one of those assertions a claim about a plausible-looking fiction.
0.10 made it checkable, and the answer is oneSGR 59— underline colour,
which changes no cell. Pinned exactly, so anything joining it has to be
read before the suite is trusted again. -
tests/cli.rsdrivestermlens-cliagainst this crate's own saved
screens: rendered, diffed with its 0/1/2 exit codes, andinspectpointed
at the real binary. The tool is resolved at the version the lockfile names,
so it and the library are one release. -
The vendored agent skill (
.claude/skills/termlens/SKILL.md) is
refreshed to 0.10.1, andcheck-skill-version.shnow fails when that copy
and the dependency disagree on major.minor. It had drifted two releases
behind with nothing to notice. -
CI writes
TERMLENS_ARTIFACT_DIRand renders failures into the job
summary via termlens'sreportaction, so a red PTY test arrives as a
picture rather than a grid in a log.
Fixed
-
Six
partial_cmp(..).expect("no NaN")sorts becamef64::total_cmp.
The issue said five; there are sevenpartial_cmpsites and six carried
anexpect, because the audit grepped the message string rather than
the construct and the two with different message text fell out of the
count. Those two were the ones that mattered:bench/stats.rssorts the
raw measured timings, andmodel/lib.rssits underspearman, a
pub fn— sospearman(&xs, &ys)with a single NaN in either column
aborted the process from safe code, with no unsafe input path required.
That is a reachable panic, not a latent one.total_cmpis total, free, and identical topartial_cmpon every pair
of non-NaN floats. NaN ordering is documented where it changes an outcome.
Insummarizeit has a consequence worth stating: a NaN fails both Tukey
fence comparisons and is rejected as an outlier; poison the quantiles
badly enough and the answer isNone, the honest summary of a sample that
has none. (#54) -
Config::block_threads()saturates instead of overflowing. It was
.product()over threeu64s read straight fromkernel.toml: a panic
in a debug build, a wrapped value in release — and a wrapped value does
not stop there, it feedsestimateand the gate'sthreads > WARP_SIZE
test, so a block of 2^64 + 32 threads reads as a legal 32-thread block.The issue proposed
checked_mulfolding toResult, which is a break to
apub fnon a crate published at 2.1.0 and could not ship in a minor
bump. It was also unnecessary, and the issue said why without noticing:
grid_blocksis the sibling doing the same job withsaturating_mul.
A proptest over three arbitraryu64axes holds the line. (#53) -
The summary invariant lives in the type.
measured_fastest_first
filtered onsummary.is_some()and returned bare candidates, so both
callers re-unwrap()ed on the strength of a filter performed in another
function. It returns(&CandidateReport, &Summary)pairs; the four
unwraps are gone because they no longer compile. Same shape in
report::build. Remainingexpects in functions that already return
Resultbecame?. (#55)
Added
-
A semver gate, with the release type forced. No workflow ran
cargo-semver-checks, so removing apub fnshipped without a signal.
The gate forces--release-type patchand the reason is measured, not
assumed: removeConfig::kernelwhile bumping the manifest to 3.0.0 in
the same PR and the inferred run prints0 checks: 0 pass, 254 skipand
exits 0, while forcedpatchprints1 major and 0 minor checks failedand exits 100. cargo-semver-checks runs only the lints the
declared bump does not already excuse, so a PR that declares its own break
silences the gate.patchexcuses nothing — and does not forbid
additions, since only breaking changes are reported.A deliberate break carries the new
breakinglabel, which switches the
job tomajor. Three of the eleven published crates are binary-only; the
eight with a library API are all covered. (#56) -
scripts/check-versions.sh, injust ci.[workspace.dependencies]
pinned the path crates at 2.0.0 while the workspace was 2.1.0 — harmless
for a path build, which is why it survived a whole minor line. Not
harmless for the next major: with the pins stale, bumping to 3.0.0 makes
cargo metadatarefuse to resolve the workspace at all. The drift
breaks the release after next. (#56) -
A rustdoc gate, and 237 documented public items. The issue said "about
42". It is 237 — I counted items and never counted fields, and 144 of
the 237 are public struct fields. In a crate family whose structs are the
JSON wire formats, the fields are the part a consumer actually reads.The gate cost nothing to turn on (
cargo docwas already at zero
warnings) and earned its keep immediately by failing on two broken
intra-doc links in the commit that added it — one naming a function that
does not exist. (#57) -
The pin watch runs weekly again, and reports a toolchain move on its own
line. The schedule had been commented out, so it spoke only when
dispatched. The two kinds of drift are not alike: commit churn is a bump
you can schedule, a channel move means our rustc can no longer build
upstream's kernels and reconverge must be rebuilt. An extractor that reads
nothing now fails the job instead of reporting "Pins current." forever.
Exercised against a stubbed upstream across five scenarios. (#51) -
The TUI's freedom from colour is now an invariant. #58 expected a
colour-only cue to vanish underNO_COLOR; measuring found there is no
such cue — every style isBOLDorBOLD | REVERSEDandColor::
appears nowhere. So the tests assert the stronger claim: every cell of all
four views has the terminal's default foreground and background, the metal
banner keeps its emphasis underNO_COLOR=1, and aNO_COLORframe is
byte-identical to one without it. The first fires the day someone marks a
refusal red. (#58)(...
v2.1.0 — read what the tool was handed
Thirteen findings, all reported against 2.0.0 with a measured reproduction.
Most of them reduce to one shape: something was consumed on a contract other
than the one it is written to, and the mismatch was reported as a fault of
whatever it was pointed at.
Changed
-
The lockstep pins move to reconverge 0.5.0, at every recorded site.
2.0.0 moved the gate to 0.4.0 and updated four of the six the policy
names, sorust-toolchain.toml,CONTRIBUTING.mdandpins.ymlstill
recorded 0.1.11 — andpins.ymlmeasures upstream drift against its own
RECONVERGE_PIN, so its weekly signal reported movement away from a
version nothing installs. That is why #17 sat open describing a pin two
releases old.just pinsnow asserts the sites agree with each other,
with no network, before anything asks upstream. Reported in #46 and #17. -
apply --verifyis a real switch. It was aboolwith
default_value_t = true, which clap gives aSetTrueaction — so
--verifyset what was already set,--no-verifywas an unknown
argument, and the help read as an opt-in for something mandatory. There
is a--no-verifynow, and the help says verification is on. This is
what makesapplyusable on a machine that has the run directory but not
the analyzer and the pinned toolchain. Reported in #36. -
prune's verdict line says what it checked. The gate answers
convergence and static shared-memory capacity at a--cc; it has no view
of instruction availability, so a crate using ansm_80+intrinsic under
needs_cc = "7.5"prunes to12 cleanat--cc 7.5and fails only when
something finally lowers it for that part.needs_ccis the author's
claim and is taken on trust — defensible, and nowhere stated, so "clean"
read as "this kernel is fine at cc 7.5". docs/LIMITATIONS.md carries the
long form, including why the two stronger fixes were not built. Reported
in #32.
Fixed
-
A kernel crate with a bin target no longer hard-stops the gate.
reconverge prints onefindings.v1document per analyzed target — its
documented contract — and this reader handed the whole of stdout to a
singlefrom_str, so asrc/main.rsbeside a kernel library, the
ordinary shape of a GPU crate, made every candidate a tool error at
trailing characters at line 2 column 1. In the Actionfail-on
defaults totool-error, so CI went red for a crate with nothing wrong
with it, pointing at the analyzer's tracker. Stdout is read as JSONL and
the findings are unioned: a deny finding in any target refuses, and the
bin target's document is harmless to merge. The fail-safe always held —
it held against a format the analyzer documents. Reported in #42. -
The scratch copy is the whole crate. It took only the entries of
src/that are files, somod util;withsrc/util/mod.rs— how Rust
code is organised past one file — produced a scratch crate that could not
compile, and the gate reportederror: could not compileagainst a crate
whose owncargo checkis clean. The message told its author to fix
build errors they do not have, or to reinstall their toolchain, and never
said that what it compiled was not their crate. It copies recursively
now, carriesbuild.rsor whateverpackage.buildnames, skips
target/, and a tool error names the scratch directory. Reported in #43. -
rustc's diagnostics survive the tool-error filter. #19 replaced a
tail-six heuristic with a filter on marked lines, and picked the
secondary marker: rustc's primary diagnostics beginerror[E0583]:,
a code before the colon. So what survived was cargo's summary and
reconverge's generic hint — "see the errors above", with the one that was
above removed. Both forms are accepted now, here and in the compile
executor, which still had the tail heuristic #19 removed next door.
Reported in #44. -
--budgetcannot be given a value that fails to bound anything.
split_at(text.len() - 1)on a trimmed-empty argument is0usize - 1,
so--budget ""panicked at exit 101. Worse:NaNand1e400parsed,
and the guard iselapsed >= budget— false for every value against NaN,
never true against infinity — so a value that looked accepted produced an
unbounded measured sweep on real silicon, which is the one failure a
budget exists to prevent. Non-finite and negative values are rejected,
the message names the flag and the accepted forms, andmin/hrare
accepted alongsidem/hbecause that is what people type.--budget 0
stays valid and means what it always did. Reported in #33. -
applydecides about verification before it prints anything. It
emitted theparams.rsand then verified, so "refusing to emit"
arrived after the emission and a reader who had piped stdout to a file
had the file. On a Metal run it could never succeed at all: that path has
no convergence gate, deliberately, so the run recordsgate_cc: "metal",
and handing that sentinel to reconverge produced the correct answer to
the wrong question ("metalis not a compute capability") dressed as a
regression ("no longer passes the gate"). Nothing regressed; the gate
never ran and cannot. It refuses by name now, before stdout, and points
at--no-verifyorprune --cc <target>.Verdicthas aDisplay, so
no user-facing message is a Rust struct literal. Reported in #34. -
A
results.jsonthe report cannot read is an error, not "unmeasured".
A truncated file, an empty one,null,[], aresults.v2from a newer
runner and a directory namedresults.jsonall rendered as "nothing
measured yet": exit 0, nothing on stderr, and a JSON report that
validated against the schema. The run directory is the hand-off between
two machines and those two conditions call for opposite actions — wait,
or go and look. OnlyNotFoundisOk(None)now; everything else names
the path and the cause, the wayverdicts.v1already did fifteen lines
away in the same function.model --resultsnames the cause too, and
tune's end-of-run report inherits all of it. Reported in #45. -
Nothing is cut mid-value or mid-word at the panel border. #24 fixed
this on the chosen line and left it in the two views below: the ranking
lost every closing bracket at eighty columns, so each interval read as a
number with no upper bound, and the rejections view — the one the README
calls the point of the tool — lost the clause that says what to do,
stopping atsplits a 64-threaand never reachingsafe only at one warp (<= 32 threads). An interval is a field and is dropped whole through the
same helper the chosen line uses; a reason is a sentence and wraps. A
scan over every golden now fails on a value or a word ending at the
border without an ellipsis — confirmed red against the shipped pre-fix
frames, and it would have caught #24. Reported in #35. -
A missing
cargo oxideis diagnosed by name. cargo's own help
relayedcargo search cargo-oxide, and cargo-oxide is not on crates.io:
cuda-oxide is a pinned git checkout. The one actionable-looking line sent
the reader to a package that does not exist, on the first wall ofstage
andtune --backend cuda. The message now names the pin, gives the three
commands CI uses, and says thatpruneneeds none of it. The pin is a
constantjust pinschecks, so a bump moves the message with it. In the
same path,exit Some(101)is an exit code again and the compile failure
reports the compiler's errors rather than its summary. Reported in #37. -
No flag on
tuneis accepted and silently ignored.--budget,
--orderand--seedare inert with--backend model, and--seedis
inert with--order exhaustiveon any backend. Each says so once, the
way--outhas since #22 — somebody who passes--budget 30mreasonably
believes something is bounded. Reported in #38.
Install
$ cargo install launchbound-cliIn CI, the action is three lines and follows 2.x on its own:
- uses: actions/checkout@v7
- uses: vyncint/launchbound/action@v2
with:
cc: "8.6"The gate installs cargo-reconverge 0.5.0 from crates.io on every run — a
cached analyzer is not evidence about what is published today.
launchbound 2.0.0 — reconverge 0.4.0, and the first ten minutes
A major, and both reasons are in the "breaking" list this project keeps in
docs/RELEASING.md: a changed CLI flag, and a change to
what the gate admits.
Changed — breaking
-
The safety gate pins
reconverge0.4.0, up from 0.3.0, and 0.4.0
reads a shared-memory length written as a named const. 0.3.0 could
only read a literal:SharedArray<f32, TILE>arrived as an unevaluated
const, the analyzer'seval_target_usizerefused it, and the static was
dropped from the RC004 budget with no finding and no diagnostic.This is not an abstract gap — it is the shape every tunable kernel has.
corpus/matmul-tileddeclaresSharedArray<f32, { TM * TK }>, and
launchbound rewritesTM/TKper candidate, so every configuration
this tool tries took the path RC004 could not see. A space with an
over-cap tile pruned as all-clean.Verified against the corpus: the six kernels' verdicts are unchanged
(their tiles are well under the cap), and raisingmatmul-tiledto
TM = TK = 128now produces
error[RC004]: kernel matmul declares 73728 bytes, where 0.3.0 said
nothing. Any kernel whose shared memory is sized by a const may now be
refused where it previously passed — which is the gate working, and is
why this is a major. -
--ccis required bytune, as it already was bypruneand
model. It defaulted to8.6, which meant the one command whose answer
you act on quietly picked a device, while the two inspection commands
made you choose.prune --cc's own help says a verdict at one capability
does not transfer to another; RC004 is a capacity check, and 8.6 offers
164 KB per SM against 7.5's 64 KB.launchbound tune <kernel> --backend modelnow asks for--cc.
Added
--ccis validated at the command line, and the CUDA spellings work.
A mistyped--ccused to be handed tocargo reconvergeonce per
candidate: eleven subprocesses forreduce-flip, 101 over the corpus, and
ninety lines of output in which the actual problem appeared nowhere. It is
now one line in ~100ms with nothing spawned.--cc 86and--cc sm_86
are normalized to8.6rather than rejected — for two digits the mapping
is unambiguous, and it is the spelling a CUDA user already has.
Fixed
-
A reconverge failure reports what reconverge said. The tool error
showed the last six lines of its stderr — a reasonable-looking default,
since a failing tool usually fails last, and reliably the wrong six:
reconverge prints its diagnosis first and its usage reference after it, so
the tail was the exit-code legend. reconverge 0.4.0 stopped printing usage
after a bad value, which fixes that case at the source; this reads the
lines markederror:regardless, because no caller controls what its
analyzer prints, and falls back to the head rather than the tail. -
modelsays that it is not gated. It ranks the whole space, and on
reduce-flipits top five are all configurations the gate refuses — the
fastest row was a kernel that hangs, under a header that carefully said
"estimated, not a measurement" and nothing about safety. It still runs no
gate and needs noreconverge; it now says so, and namestune --backend modelas the gated form. -
tune --backend modelno longer leaves an empty run directory. The
directory was created before the backend match, for every backend, and the
model path writes nothing — so every run litteredruns/, which is
checked in, andlaunchbound reporton it failed withverdicts.json: No such file. An--outgiven to this backend is now answered rather than
silently ignored. -
launchbound-tui --helpprints help. It readargs()directly, so
every flag was taken as a run-directory path:--helpcame back as
run dir: --help/verdicts.json: No such file or directory, which reads as
a broken tool.-h,--help,-Vand--versionanswer; any other
leading dash is reported as an unknown option, which is what stops the
next flag landing here as a path. This is a published binary. -
The chosen configuration's interval is dropped, not cut. At eighty
columns — the default terminal size, and the width this suite mandates —
the line ended0.0400 ms [0.0398,: a number with no upper bound and a
dangling comma, on the one line carrying the result. The interval now goes
whole when it does not fit; at 110 columns it is unchanged. -
The TUI goldens wait for a finished frame. They synced on a 150ms
quiet period, which is a guess at how long a repaint takes; on a loaded
runner the app pauses mid-repaint and the screen read is half-painted.
This had already cost the suite once —ranking_scrolls_a_long_candidate_list
carries a comment about a golden blessed from a too-early capture, which
then verified nothing while passing — and the same shape failed
reconverge'smainon macOS. The binary already brackets every repaint in
DEC 2026 synchronized updates, sowait_frameobserves only whole frames.
The 100-iteration stress gate went from 15.8s to 0.7s.
Migrating from 1.x
- The Action's floating tag is now
@v2.uses: vyncint/launchbound/action@v1keeps working and stays on 1.2.0, which is
the point of a floating major tag — but it stays on reconverge 0.3.0 with
it, and 0.3.0 is the analyzer that cannot see a named-const shared-memory
size. Move to@v2to get the gate this release is about. - Add
--ccto anylaunchbound tuneinvocation. It has no default now.
Documentation
- The CLI table listed
launchbound tui <run>, which is not a subcommand —
the binary islaunchbound-tui. It also omittedmodel, and showed
tunewithout the--ccit now requires. - The Action's input table still gave
reconverge-versionas0.1.11, two
releases stale.
Install
cargo install launchbound-cli --locked # the CLI
cargo install launchbound-tui --locked # the run browserIn CI, the Action:
- uses: vyncint/launchbound/action@v2
with:
kernel: path/to/kernel
cc: "8.6"@v1 keeps working and stays on 1.2.0 — with reconverge 0.3.0, the analyzer
that cannot see a named-const shared-memory size.
The gate needs cargo-reconverge 0.4.0 and reconverge-driver 0.4.0, built
by nightly-2026-04-03; the Action installs both. Locally:
cargo install cargo-reconverge --locked --version 0.4.0
cargo +nightly-2026-04-03 install reconverge-driver --locked --version 0.4.0launchbound 1.2.0
Changed
-
The safety gate now pins
reconverge0.3.0, up from 0.1.11 — two minor
versions of analyzer the gate was not getting. The pin moves in four places:
the corpus workflow, the Action'sreconverge-versiondefault, and the two
documents that name it.The gate admits exactly the same set. A newer analyzer can change what
the gate refuses, which is a change in product behaviour rather than a
dependency bump, so the corpus was re-run under both versions on the same
toolchain and compared: 93 clean, 8 refused, 0 caveats, 0 tool errors,
and the two runs are byte-identical — same candidate hashes, same
REFUSED RC001lines, same reasons. The eight refusals arereduce-flip
above one warp, which is the corpus's known flip and the behaviour the gate
exists to produce. The measurement is recorded in
docs/research-baseline.md.No toolchain change was needed: launchbound and reconverge 0.3.0 already pin
the samenightly-2026-04-03, so the rule that the analyzer and the
toolchain move together is satisfied without moving either.What this does not establish is general equivalence. reconverge gained
multi-warp replay, bounded inlining and unmasked warp-wrapper analysis
between these versions; a kernel exercising those paths could be decided
differently. Six kernels are the evidence, and six kernels are what they are. -
docs/LIMITATIONS.mdnow describes the limits of the analyzer the gate
actually runs, and carries the date it was re-checked.
launchbound 1.1.0
Added
-
The overview shows the field, not just the winner. Under the chosen
configuration it now lists every measured candidate fastest-first, with what
each costs relative to the fastest. An autotuner's output is not a
configuration, it is the claim that the configuration is worth choosing,
and that claim is unreadable without the alternatives: a field inside a
percent says the tuning did not matter, one spanning 4x says it did.The percentage is anchored on the fastest measured candidate rather than
on the chosen one, deliberately. Where a refused configuration measured
faster — the case the overview already warns about in bold — anchoring on
the chosen would bury the number that matters. On thereduce-flipfixture
it now reads plainly: the disqualified candidate is fastest and the safe
choice costs 90.5%.The list is the head of view 2's, from one shared ordering, so the two
cannot disagree. Where it does not fit, it truncates and says how many more
view 2 holds.
Changed
- The overview fills the pane it was given. It drew four lines into a
twenty-five-row box; the rest was blank. - termlens 0.5 → 0.6 for the TUI test harness. No source change was
needed: the only breaking change in 0.6 isGraphicsSeenbecomingClone
rather thanCopy, and this suite asserts on text. What the upgrade buys
is a fix that matters here —openptyis now retried when the machine is
briefly out of PTY devices, which macOS is whenever a suite runs one test
per core.
CI
- The stress workflow gained a flake hunt. The existing gate is unchanged —
the TUI suite once per OS on every push and pull request. The hunt runs the
same suite many times over, split across five machines that each use a
different--test-threads, on dispatch or weekly. Five machines because a
race that only loses on a slow runner gets five rolls rather than one; five
concurrencies because that is the axis a PTY suite's faults live on, and
running one point five times only samples that point harder. - A published-package check, at release and weekly:
cargo install launchbound-cliinto a clean directory from crates.io, then real work with
no GPU, no network and no checkout — enumerating a config space whose
constraint rules out exactly one of six pairs. It is the only check here
that can fail without anybody touching the repository, becausecargo installresolves dependencies fresh where CI resolves against the lockfile. - The gate job no longer caches the analyzer. It cached
cargo-reconvergeandreconverge-driverand skipped the install on a hit;
a cached binary is not evidence that the gate holds against the published
analyzer. Nothing in this repository's CI or its action caches anything now. - Squash merges no longer fail the DCO check. GitHub rewrites a
web-flow squash commit's author email after the sign-off is written, so the
exact match the check required was impossible by construction. Such commits
must still carry a sign-off; every other rule is unchanged.
launchbound 1.0.2
Review-driven fixes: honest unmeasured labeling, tag-version publish guard, fully hermetic GitHub Action (every step toolchain-pinned; reconverge now installed from its crates.io releases via the renamed reconverge-version input), single-source pins in workflows, and the release checklist. See CHANGELOG.md.
launchbound 1.0.1
The safety gate as a GitHub Action (uses: vyncint/launchbound/action@v1), README badges, termlens 0.5, and releases now publish through crates.io Trusted Publishing (OIDC — no stored registry token). See CHANGELOG.md.
launchbound 1.0.0
A convergence-safe autotuner for Rust GPU kernels: searches a cuda-oxide kernel's launch and specialization space, refuses convergence-unsafe configurations (reconverge gate, one run per specialization), measures on CUDA silicon or Metal, and reports every configuration that was faster and refused — with rule ID and source span. See docs/LIMITATIONS.md for the honest edges. All 11 crates on crates.io; install the CLI with: cargo install launchbound-cli