Releases: SShadowS/LethAL
Release list
v0.1.0-alpha.2
The first release with a machine-readable surface for agents and CI, a demo application you can
point the tool at, and a licence. Roughly thirty roadmap rows closed since alpha.1; the ones a user
can see are below, each citing its R<n> for the evidence.
Added
- A licence. MIT. There was none, so nobody could legally use a binary they downloaded.
lethal explain --top <n>(R150), because the projection built for agents did not fit one:
a 473-mutant report projects to 243 KB. The output now always carriessurvivorSelection
(total,shown,omitted,rankedBy), present even when nothing was capped, so a truncated
list can never read as a complete one. A cap ranks survivors by how much evidence each carries,
in a total order, so the same report and cap give the same rows on any machine.lethal doctor --json(R151). The read-only pre-flight was the one surface an agent could not
parse. EmitsdoctorSchemaVersion,ok, per-checkname/ok/detail,notCheckedtokens for
what a GREEN report does not cover, and a caveat with a machinekind. Same exit code; only the
rendering changes.--jsonis refused elsewhere rather than ignored.- An agent contract (R153):
docs/using-lethal-from-an-agent.mdand a copyable skill at
skills/lethal-mutation-testing/SKILL.md, both checked against the code by a test — every flag
they name must exist, and the exit codes and schema versions they promise must match the
constants. - Published JSON Schemas for the
explainprojection anddoctor --json, underschemas/
(R152). Pinned against the TypeScript declarations in both directions, with every enum asserted
equal to its runtime constant. The report and the event stream do not have one yet. - A demo application,
examples/gift-card(R155): a small store-credit extension whose eight
tests are green and do not notice that deleting oneSetRangemakes a card's balance the whole
store's liability. Measured live: 36 mutants, 20 killed / 9 survived / 7 no-coverage, 13.8 s, and
all 36 verdicts were pre-committed before the run and all 36 matched. Its rehearsal report ships
too, solethal explaincan be exercised with no server. --operator <name>(R127) to scope a run by operator, so measuring one operator on a real
project no longer costs every other operator's mutants in the same files.- Four Tier-2 operators and one extension:
flip-filter-literal(R134, the first operator that
mutates a filter string BC re-parses at runtime rather than AL thatalccompiles),
swap-find-direction,validate-to-assign, andswap-modify-flagextended fromModifyto
Insert/Delete(R136). Every one landed with its per-mutant verdicts pre-committed and matched
against a live container. declarativeSitesin the report (R144). Sites LethAL declines to mutate because they are
declarative properties were counted and then thrown away in a warning; the report now carries
them.- The BC build that produced the verdicts is recorded on the al-runner path (R129).
lethal doctorworks for an al-runner-only project (R146) instead of refusing it.- A source-overlay renderer,
scripts/render-overlay.ts: a run drawn on the code it measured, with
"ran" at procedure granularity (which is all coverage knows) and "checked" per site. - CI and release workflows (R154).
validity.executionContexton every report (R60). LethAL executes every mutant headlessly,
so every verdict describes your app's NON-interactive branch, while a developer running the same
suite from VS Code runs GUI-allowed — the two are not measuring the same code, and nothing said
so. Required rather than optional, and printed on every run including a clean one, because the
reader most likely to quote a score without qualification is the one whose run had no other
caveats. Measured before it was written (scripts/measure-gui-guarded.ts, Continia Document
Output, 551.alfiles): 62 of 19,850 mutation sites — 0.3% — sit inside a
GuiAllowed/Confirm-guarded branch, which is why this is a stated limit rather than a
per-site signal. Note the three constructs differ:Messageis a no-op,Confirmforces its
DEFAULT answer (so the non-default arm is the unreachable one), andPage.RunModalerrors.bcdev.altoolPathconfig override (R64) to pin the publish tool, alongside the existing
bcdev.alcPath(R43) for the compiler. The publish tool build is not interchangeable either: the
AL extension's bundledaltool17.0.2273547 silently ignoresBC_SERVER_USERNAME/
BC_SERVER_PASSWORDforpublishappand itsauth loginis AAD-only, while the
microsoft.dynamics.businesscentral.development.tools18.x dotnet tool reads them and publishes.
The two overrides are independent, so compile and publish can run on different builds — and,
set together, they now satisfy the "no AL extension installed" gate on the container path as
well, which previously demanded the extension no matter what the config named.
Changed
- Vendored tree-sitter-al 3.2.1 to 4.0.1 (R133), a breaking grammar release that moves named
trees LethAL hashes. Thirty-two empty-block identity hashes were re-keyed with per-site proof and
all six live gates re-measured unchanged. - al-runner stops re-downloading 230 MB of platform apps per invocation (R147, R130): the
platform-app directory is pinned with--package-cache, and the double provisioning inside a
single invocation is gone. 17.1 s per invocation became 6.8 s. - The platform-artifact screen no longer tags every
Insertmutant (R143): the tag is dropped when
the receiver'sOnInsertprovably does not assign the primary key, so the screen stops
over-reporting. A receiver that cannot be resolved keeps the tag.
Fixed
-
A trigger mutant could score
survivedwhen its only covering test was red (R140), where
member-level attribution correctly declines to score at all. -
A stale published TEST app was indistinguishable from genuinely failing tests (R139) and cost
a full gate run to diagnose. -
Conformance refusals that asserted nothing (R137, R142): an empty expectation passed on any
input, and a non-empty one never checked for extra specs. -
Two more
errorshapes recorded nocause, one of them labelleddeadline-exceededwhen it
was not one (R122). -
Eight
scripts/files did not compile under the repo's own strict flags, and two had produced
numbers a roadmap decision was made on (R120). -
A permissions refusal at baseline discovery no longer reads as an unsupported test type
(R35). R27 taught LethAL to name theTestPermissionscause, but only on theunstablepath.
A test BC refused at baseline discovery never reaches it: the test was dropped from the green
set and the mutants it alone covered were recordederrorwith the note "unsupported test
type", pointing the reader at their test's type when the fix is one property
(TestPermissions = Disabled) in their own source. Those mutants now carry a note naming the
permissions cause and quoting BC verbatim, the report gainspermissionsRefusedand a
tests-permission-refusedcaveat, and the same refusal recognised on theunstablepath feeds
the same field — so a run can no longer disagree with itself about whether it hit one.
Known limitation: the detector matches BC's English refusal text, so a non-English server
still gets a silent miss (never a wrong answer) — tracked as R66 and pinned by a test. -
A failed tool spawn could report nothing at all (R65). A Bun spawn
ENOENTarrives with an
EMPTYmessage, so catches that stringifiederr.messagereported a blank cause — how R64's
wrong-platform binary presented, and why it took a long external session to trace.alc,
altooland the configured environment tool now report the OS error code, syscall and path. -
The bcdev backend on Linux and macOS hosts (R64).
defaultAlToolPaths()hardcodedbin/win32/
regardless ofprocess.platform, so every non-Windows run spawned a Windows PEalc.exe/
altool.exethat cannot execute — including anyone using the released Linux and macOS binaries,
for which the bcdev backend could never have worked. The failure surfaced many layers up as an
opaque, message-lessError. The AL Language extension ships per-RID builds side by side
(bin/win32/{alc,altool}.exe,bin/linux/{alc,altool},bin/darwin/{alc,altool}, verified
2026-07-31 againstms-dynamics-smb.al-18.0.2498801); LethAL now picks the one matching the host,
and refuses loudly on a host the extension ships no build for instead of guessing a RID.
Reproduced the frozenitest:bcdevbaseline exactly — 3 killed / 10 survived / 3 no-coverage,
authoritative,baselineGreen— against a live Linux Docker BC 28.1 container, with the run
costs recorded indocs/benchmarks/runs.jsonl(the ledger's first plain-container rows).
Security
- Six committed campaign reports carried 1,857 fields of a third party's AL source in this
public repository. They predate the redaction ruling and were never swept. Redacted, and the
guard now discovers the report set by glob instead of naming two paths by hand, which is how it
missed them.
Install: download the binary for your platform. It is standalone and needs no Bun,
Node or npm. Check it with lethal --version, which prints the commit it was built from
and the mutation operators it can actually apply, so a stale binary cannot pass for a
current one.
Windows binary: Authenticode-signed via Azure Trusted Signing, and the signature is verified in CI before publishing. The Linux and macOS binaries are NOT signed: Authenticode is a Windows PE format and macOS needs separate notarisation.
lethal-control.app is the LethAL Control extension, attached to this release by
hand (building it need...