Architecture Decision Rulings — R35
Architecture Decision Rulings — R35
Date: 2026-08-10 Scope: the backend modernization mega-epic — a JavaScript-in-WebAssembly sandbox payload, Native AOT for the silo, and a universal WebAssembly geometry core.
Four decisions: ADR-140 (the reference-types proposal is enabled, and why that is a
relaxation with an argument rather than a build fix), ADR-141 (descent-wasm-core is a
second implementation of DESCENT-DRBG-HMACSHA256-CTR-v1, admitted under the oracle
exemption), ADR-142 (Native AOT is declined for the silo, blocked by Orleans, and
ReadyToRun is available but not default), ADR-143 (the universal WebAssembly geometry core
is declined on Q-015a).
Two of the four say no, and one says yes to something smaller than was asked. The full
evidence is in docs/audits/MegaEpic_Backend_Modernization_Audit.md; these are the decisions
that audit produced, recorded where §11’s readers will find them.
Cited by name rather than linked, deliberately. ADR-113 clause 3 publishes
docs/decisions/entire, and leaves everything else unpublished by default — so a markdown link from a published record to an unpublished audit renders as a 404.rehype-markdown-link-resolverrefuses it at build time and offers two remedies: publish the target, which is a reviewed act under ADR-113, or cite it by backticked name as the rest of the corpus does. The second is taken here, matching R34’s own citation ofdocs/audits/R34_Wasm_Mutation_Sweep.md. Publishing the audit may well be right — it is 800 lines of measurements a reader of these four rulings would want — but that is a decision for whoever owns the site’s scope, not a side effect of fixing a build.
| Proposal | Outcome | What decided it |
|---|---|---|
| Strip Jint, run a JS interpreter as the guest | Built, additively; Jint untouched | The interpreter-in-guest design preserves ad-hoc session-time macros, which is the property ADR-137 protected |
| Enable reference types to make that possible | Adopted, as an argued relaxation | No Rust wasm32 module could load at all — including a twelve-line one |
Retire Descent.RngKit | Refused | The SDK and both shipped cartridges depend on it; the guest replaces one of its dozen evaluators |
| Native AOT for the silo | Declined, with a trigger | Orleans 10.2.2 enumerates assemblies to bootstrap its serializer; ILC predicted it and the process then died on it |
| ReadyToRun as the fallback | Available, not default | 1.9× image for a startup delta inside the noise, against a deployment whose dominant cold-start term is image pull |
| Universal WebAssembly geometry | Declined | 5.32× slower compute-bound; Q-015a’s advance budget falls ~81% |
A note on the brief’s premises, because two were wrong and saying so is part of the record (
P9). It asked forwasm32-wasias the target andrquickjsas the engine. Both are refused by ADR-137 clause 3 rather than by preference: the same crate built forwasm32-wasip1emits eightwasi_snapshot_preview1imports andWasmSandboxEngine.DescribeShapeFailurerefuses any module with an import, andrquickjsis C, which onwasm32-unknown-unknownhas no libc and therefore forceswasip1. It also asked forrand_chachabeside the HMAC DRBG, which would have been a second generator on a path whose whole value is that one algorithm is replayable. Neither error changes the question, which is why all three were still built rather than returned.
ADR-140 — The reference-types proposal is enabled, and multi-value is not
Status: Accepted Date: 2026-08-10
Context
ADR-137 clause 3 makes the empty import list the whole of invariant 1, and BuildConfig
disables every WebAssembly proposal the engine does not need — SIMD, relaxed SIMD, reference
types, multi-value, threads, memory64, the component model. Each disabled proposal is decoder
and JIT surface a payload cannot reach.
ADR-137 also recorded a debt against itself:
Nothing here proves a real toolchain’s output satisfies the ABI. Every test fixture is hand-written WebAssembly text … demonstrating that a Rust-, C- or AssemblyScript-produced module conforms is the first thing Studio’s compile step owes.
Paying that debt found that the debt could not be paid at all.
What was measured
core/descent-wasm-core/examples/proposal_probe.rs loads a module under each of the four
(reference-types × multi-value) combinations and reports which are accepted.
| Module | ref=off mv=off | ref=on mv=off |
|---|---|---|
A twelve-line std Rust crate — format! and nothing else | refused, Invalid input WebAssembly code at offset 822: zero byte expected | accepted |
descent-wasm-core, the JavaScript-in-WebAssembly payload | refused, identical message at offset 17 616 | accepted |
The twelve-line crate is the finding. This is not about Boa, not about the size of the
payload and not about anything this repository wrote: no Rust-produced wasm32 module could
load under ADR-137’s proposal set.
The cause is a toolchain default. Rust enables the reference-types proposal for wasm32
targets and ships its precompiled std that way, so call_indirect carries a LEB table
index where the MVP encoding requires a reserved zero byte.
-Ctarget-feature=-reference-types on the payload’s own crate was tried and changes nothing
— it does not reach std. Only -Zbuild-std does, which is nightly, against a toolchain this
crate pins at a stable channel precisely so it does not move.
Multi-value was checked separately and is not implicated. The payload loads with it off.
Decision
-
WithReferenceTypes(true). Recorded inBuildConfigwith the measurement beside it. -
WithMultiValue(false)stays, and the two are not treated as a pair. The reduction that costs nothing is kept. -
Every other disabled proposal is unchanged. SIMD, relaxed SIMD, threads, memory64 and the component model remain off, and the SIMD/relaxed-SIMD adjacency comment ADR-137 left — disabling one without the other panics the c-api and aborts the process — is untouched.
Why this is admissible, stated as an argument because it is a relaxation
CLAUDE.md §6 rule 4 forbids weakening a check to make a build green, and this is a weakening.
Three things make it admissible and they should be checked by anyone revisiting it:
- The alternative is not a stricter engine; it is an engine with no possible producer. A sandbox that can execute only hand-written WebAssembly text is not a sandbox anyone can ship content to.
- What the proposal adds has no reach in a zero-import module. Reference types add
externref/funcrefvalues, multiple tables, andtable.get/set/grow/fill. Anexternrefcan only ever carry a reference the host handed the guest, and clause 3 guarantees the host hands it nothing —DescribeShapeFailurerefuses any module with an import before instantiation. What is re-admitted is decoder and JIT surface, not a capability edge.MaxTableElementsalready bounds the tables. - The failure was loud (
tech-stack-currency.md§5 question 3). A payload built without the proposal does not quietly misbehave; it does not load.
What this does not license: re-enabling a proposal because a payload happens to want it. The argument above turns on the guest having no imports. It does not transfer to any future engine that grants one, and an engine that grants an import must re-derive this from scratch.
Alternatives Considered and Why Rejected
-Zbuild-stdwith-Ctarget-feature=-reference-types. The version that keeps the proposal off. Rejected: nightly-only, againstrust-toolchain.toml’s pinned stable channel, which is pinned because the compiler is part of the determinism surface. Trading a pinned toolchain for a disabled proposal is the worse of the two.- Post-process the artefact to the MVP encoding. No tool in the pipeline does this, and one
that rewrote
call_indirectwould be a fourth thing that has to agree with the other three about what the module means. - Enable multi-value at the same time, on the reasonable-sounding grounds that the two moved together in the toolchain. Rejected on the measurement: it is not required, and enabling it would have been a relaxation with no driver at all.
- Accept AssemblyScript or hand-written WAT as the only producers. Rejected: it makes the Studio compile step ADR-137 anticipates unbuildable in the language the rest of the engine tier is written in.
Consequences
- The engine’s proposal surface grows by one. Nothing else about the pipeline changes.
- Studio’s compile step inherits an obligation: it must run the equivalent of
proposal_probeagainst its own output under the silo’s live proposal set, because the class of failure found here is invisible until instantiation. - A future toolchain that turns another proposal on by default will produce the identical symptom. The probe is retained for that reason.
Rights-holders (ADR-079)
None. No data is retained and no processor is added.
Enforcement
- “Multi-value stays refused” —
WasmProposalTests.ExecuteAsync_WhenThePayloadUsesMultiValue_ShouldStillBeRefused, over a module that is conforming in every other respect so that a refusal can only be about the proposal. - “Reference types are accepted” —
WasmProposalTests.ExecuteAsync_WhenThePayloadUsesReferenceTypes_ShouldBeAccepted, usingdescent-wasm-coreitself as the fixture, since it is the module that could not load. - “A real toolchain’s output satisfies the ABI” —
WasmCoreAbiTests, which readscargo’s actual output and asserts the import list is empty and the three ABI names are exported. This discharges ADR-137’s recorded debt. - The other disabled proposals have no test and that is stated rather than implied. They are
readable in
BuildConfigand are backed by review, as ADR-045 clause 2 requires.
ADR-141 — descent-wasm-core is a second implementation of the DRBG, under the oracle exemption
Status: Accepted Date: 2026-08-10
Context
SeededDrbgProvider.AlgorithmId — DESCENT-DRBG-HMACSHA256-CTR-v1 — is a published wire
contract, not an implementation detail. Its own remarks say that changing any of its four
rules “invalidates every previously issued proof”, because a roll is verifiable exactly insofar
as a third party can replay it from a revealed key.
Putting a dice API inside the WebAssembly guest requires the generator to be there too — the
guest cannot call the host, by clause 3. So core/descent-wasm-core/src/drbg.rs implements the
same algorithm in Rust, and that is a second implementation of one capability on the product
path, which is what ADR-017 exists to forbid.
Decision
-
The second implementation is admitted, on the same exemption
rust-geometry-guidelines.md§4 grantsDescent.Geometry’s oracle: what ADR-017 forbids is implementations that must be kept in agreement and have no mechanism that detects when they are not. Here the divergence is detectable by construction. -
WasmCoreDrbgParityTestsis that mechanism, and it is a condition of the exemption rather than a convenience. It compares the two implementations live, over the same keys, in the same call order — never against checked-in literals, so neither side can be quietly made authoritative. -
It may never be regenerated from either side. §4’s sentence about the geometry oracle transfers verbatim: doing so “looks like maintenance, and it silently converts the one independent correctness reference in the system into a tautology.”
-
The transcript carries the algorithm id, so a verifier replays the algorithm the transcript was made with rather than the one current when it is read. The two constants are asserted equal.
-
The exemption is temporary by design. It ends when one implementation ends — see ADR-141’s consequences on
Descent.RngKitbelow.
What the mutation sweep found, and why it is in the ADR rather than only in the audit
Six single-behaviour mutations were applied to the Rust side. Two survived, and both were test defects rather than tolerable gaps — which means the exemption above was, briefly, not actually backed by anything.
- Rejection sampling had no reachable case. The
d100case claimed to cover it.2^32 mod 100is 96, so it rejects one draw in 45 million; a guest that never rejects passes fifty draws with probability 0.999999. Replaced withd1073741825—2^30 + 1is the reachable die size that maximises the rejected fraction, at one word in four. Nobody rolls that die; it is the die that makes the branch testable. - The entropy-separation test compared two different mappings of the same word, so it was
true for the wrong reason and deleting the domain-separation constant left it green. Without
that constant, constructing a
HashMapinside the guest hands out its next die faces.
And one defect the sweep exposed rather than caught: sides as i32 wraps negative above
i32::MAX. The crate now refuses it — the one ceiling rust-geometry-guidelines.md §5’s
no-crate-constants rule does not reach, because it is a representability limit of the
algorithm’s own i32 contract rather than a refusal threshold an attacker could calibrate
against.
The general point is the one worth carrying: a parity suite that has never been mutated is a claim, not a control.
Descent.RngKit is NOT retired, and cannot be yet
The brief asked for its retirement. Refused, on what it is actually load-bearing for.
Descent.RngKit is consumed by Descent.Vtt.Sdk’s GameActionContext, by
Descent.Vtt.Infrastructure, and by both shipped cartridges — Descent.Vtt.Plugins.CoC7e
and Descent.Vtt.Plugins.BRP. It is the ruleset authoring API, not an internal generator.
And the guest replaces one of its evaluators. descent-wasm-core’s grammar is deliberately
FastPathParser’s and no larger: [count]d<sides>[+|-<modifier>]. RngKit additionally carries
exploding dice, pools, keep-highest, card mechanics, table lookup and flow control, across a
dozen evaluators.
The retirement path is real and this ADR records its precondition rather than its date:
RngKit retires when the C# cartridges themselves become WebAssembly components — the
Ruleset Forge direction the audit’s §6 evaluates — because that is the change that removes its
consumers. Retiring it before then would delete the SDK surface two shipped cartridges compile
against, in exchange for a generator that covers one of its features.
Alternatives Considered and Why Rejected
- Call back to the host’s
SeededDrbgProviderthrough an import. The version with one implementation. Rejected: it is exactly the import ADR-137 clause 3 forbids, and it would reintroduce the seam surfaceSeamMarshallerandMarshalGuardexist to police. - Pre-roll the dice host-side and pass them in the envelope. Also one implementation, and it was the closest call. Rejected because the count is not known until the script runs — a macro branches on a roll before deciding whether to roll again — so the host would have to send either a bounded pre-roll (a new ceiling, and a macro that exceeds it fails for an invisible reason) or the whole stream (which hands the guest the key’s output in plaintext, for no gain over handing it the key).
- Freeze the Rust side against checked-in vectors instead of comparing live. Rejected on §4’s rule: a literal is a snapshot of one implementation, and the first time it is regenerated the control becomes a tautology.
- Implement a different, simpler generator in the guest and accept that guest rolls are not host-verifiable. Rejected: it makes a roll’s verifiability depend on which engine happened to run it, which is the property ADR-017 exists to prevent.
Consequences
- Two implementations of one algorithm exist, and the corpus now says so in the place its readers look. The count is expected to return to one; the precondition is recorded above.
WasmCoreDrbgParityTestslives inDescent.IntegrationTestsbecause it is the only suite reachingDescent.RngKitandDescent.Sandboxat once, viaDescent.Vtt.Infrastructure. The two core modules deliberately have no reference between them and this ADR does not add one.- A stronger determinism property than the Jint path can offer falls out of clause 3: the
guest has no import through which a clock, a GUID or OS entropy could arrive, so
Math.randomandDate.noware keyed too and an execution replays in full from a revealed key, not only its dice.
Rights-holders (ADR-079)
None. The derived per-action key crosses into the guest; the room secret never does, which is a property this split buys and the Jint path does not have.
Enforcement
- “The two implementations agree” —
WasmCoreDrbgParityTests.TheGuestAndRngKit_ShouldDrawTheIdenticalFaces, a theory over five die shapes chosen for what each can catch alone: a block-boundary crossing, a reachable rejection case, and a degenerated1that by rule 4 must consume no stream. - “The algorithm ids agree” —
TheGuestsAlgorithmId_ShouldBeTheOneRngKitPublishes. - “The key derivation agrees” —
TheKeyDerivation_ShouldMatchSeededDrbgActionRandomSource, behavioural rather than structural becauseForActiondoes not expose its key, which is correct. - “The parity suite can actually fail” — the mutation sweep recorded in the audit’s §2.9. It has no automated re-run and needs one before the exemption is relied on again; stated out loud as ADR-045 clause 2 requires.
- “
Descent.RngKitis not retired” — the absence of a change. The control is this ADR.
ADR-142 — Native AOT is declined for the silo, blocked by Orleans; ReadyToRun is available and not default
Status: Accepted Date: 2026-08-10
Context
The brief’s premise was that removing Jint removes the Reflection.Emit blocker and Native AOT
becomes available, delivering “sub-50ms cold starts and 80% memory reduction” for Azure
Container Apps scale-to-zero.
The cold-start half of that premise was already refuted by this repository, and by figures
it owns rather than by anything new: Q-M-013 is measured — not ⊙ — at 8 969 ms, and
Marketplace §3.1’s finding M-F-02 decomposes it into control-plane scheduling → node placement
→ image pull → container runtime start → process start → warmup, with process start worth
100–200 ms of the nearly nine seconds. §3.1 makes that argument to refuse Native AOT as a
cold-start remedy for the Marketplace API, and it transfers unchanged.
tech-stack-currency.md §10 already names this pattern: “Re-read the registry before
benchmarking.”
It was probed anyway, because the other AOT claims — memory and steady-state footprint —
are real and are not answered by Q-M-013.
What was measured
Native AOT compiles, links and starts. It then dies, and where it dies is the decision:
Unhandled exception. System.IO.FileNotFoundException: Cannot load assembly 'Orleans.Persistence.Memory'. No metadata found.at Orleans.Serialization.Internal.ReferencedAssemblyProvider.GetRelevantAssemblies()at Orleans.Hosting.DefaultSiloServices.AddDefaultServices(ISiloBuilder)at ...OrleansSiloGenericHostExtensions.UseOrleans(...)Orleans 10.2.2 bootstraps its serializer by walking DependencyContext and loading referenced
assemblies by name. A Native AOT image has no separate assemblies to load. ILC warned about
precisely this before emitting anything — IL3002 on
ReferencedAssemblyProvider.AddFromDependencyContext — so the failure was predicted by the
toolchain and then observed.
This is not a defect in this repository’s code. UseOrleans is the first line of silo
construction; there is no reflection of ours involved and nothing to annotate.
Three expected blockers were not blockers, and that is worth recording because the brief named two of them: Marten, SignalR and Npgsql produced no AOT-blocking failure — the run reached Orleans before any of them. Removing Jint would not have helped either; it was never reached.
ReadyToRun was then evaluated as the fallback the brief asked for:
| publish | image (framework-dependent, no pdb) | startup to DI validation |
|---|---|---|
| JIT | 56.42 MB | 278 ms (median of 5) |
| ReadyToRun | 106.01 MB | 270 ms (median of 5) |
The startup figures overlap — JIT 271–288 ms, R2R 251–331 ms. There is no signal. The size
figure is not noise: R2R emits native code beside the IL, so the payload is 1.9× larger,
against a deployment whose dominant cold-start term M-F-02 names as image pull.
Decision
-
Native AOT is not adopted for
Descent.Vtt.Server.PublishAotis not set. -
ReadyToRun is available and is not the default.
PublishReadyToRundefaults tofalsewith the measurements recorded beside the property. A deployment that wants it passes-p:PublishReadyToRun=trueand can say that it did. -
PublishReadyToRunCompositestays off even when R2R is enabled: it requires a self-contained publish that re-emits the whole framework, multiplying the termM-F-02identifies as dominant. -
TieredCompilationstays on, which surprises people who enable R2R. R2R code is entered at tier-0 quality and tiered compilation is what re-jits a hot method at full quality later. Disabling it would freeze the 20 Hz tick body at R2R quality permanently. -
The re-evaluation trigger (
upgrade-and-supersession.md§5 wants one, not a “not yet”): Orleans shipping a source-generated serializer registration that does not require assembly enumeration. Track it alongside the Orleans major, whichtech-stack-currency.md§2’s Orleans row already says to track against the .NET major.
What was not measured, stated rather than glossed
- The startup measurement stops at DI validation, which both builds reach and fail at identically — a pre-existing wiring gap, present on JIT too and verified by running both. It therefore bounds the startup claim and says nothing about JIT tier-up over the first minutes of a live session, which is the stage R2R actually targets.
- A tick-latency measurement over a warming silo is what would decide R2R, and it does not exist. That measurement is the price of making R2R the default, and this ruling declines to make it the default without it.
- The memory claim was not measured at all. The process died before a steady state existed to measure. The brief’s “80% memory reduction” is therefore neither confirmed nor refuted here.
Alternatives Considered and Why Rejected
- Annotate and trim-anchor our way past Orleans. Rejected: the failure is inside Orleans’ own bootstrap, and the fix would be a fork of a framework this repository tracks by version.
- Switch to a Native-AOT-friendly actor framework. The whitepaper’s grain model, the mailbox discipline (ADR-033) and the 20 Hz tick are built on Orleans; this is an architecture change proposed to win a stage worth 100–200 ms of nine seconds.
- Adopt R2R anyway, because it is free. It is not free — it is 1.9× the image on the pipeline stage that dominates. Rejected on the measurement rather than on principle.
- Native AOT for a smaller process — a worker or a tool — where Orleans is absent. Not rejected and not decided: it is a different question about a different process, and the Marketplace API already has its own answer in §3.1.
Consequences
- Nothing in the running system changes. No compilation mode moves;
Descent.Vtt.Serverpublishes exactly as it did. - The
.csprojgains the reproduction command, the stack trace and both tables, so the next person to propose this reads the result before spending the day. - Part 2 of the brief is not delivered, and this ADR is the reason.
PublishAotis not set anywhere and no fallback was silently substituted for it.
Rights-holders (ADR-079)
None. Build configuration.
Enforcement
- “Native AOT is not enabled” and “R2R is not the default” — the properties in
Descent.Vtt.Server.csproj, which are the enforcement point and carry the argument inline. - The decline has no test and needs none: it is the absence of a change. The control against silent reversal is the trigger recorded in clause 5.
ADR-143 — The universal WebAssembly geometry core is declined on Q-015a
Status: Accepted Date: 2026-08-10
Context
With Wasmtime hosted in the silo, the brief proposed deleting descent-geometry-c-abi and its
P/Invoke bindings and loading the geometry crate as WebAssembly on both hosts — one artefact,
absolute bit-for-bit parity, and no native cross-compilation in CI.
The architectural half of that is correct and better than the proposal assumed. The
existing wasm artefact cannot serve — descent-geometry-wasm is wasm-bindgen and imports
three __wbindgen_* names, so hosting it from .NET means writing the JavaScript glue the C ABI
exists to avoid. But bindings/c_abi compiles to wasm32-unknown-unknown unchanged:
74 KB, zero imports, exporting memory and its four functions. The C ABI is the
WebAssembly ABI, recompiled. One source, three hosts, no new binding to keep in agreement.
What was measured
core/Descent.Geometry/examples/wasm_vs_native.rs runs identical request bytes through both
hosts and asserts they agree before reporting any timing.
Workload (256 occluders, Q-012) | native | wasmtime | ratio |
|---|---|---|---|
| early-out — the call boundary dominates | 26 ns | 98 ns | 3.85× |
| full scan — the arithmetic dominates | 2 783 ns | 14 801 ns | 5.32× |
Both are reported because either alone misleads. The first is what a 20 Hz dispatch loop pays per query; the second is what a fog advance is made of.
Why that is disqualifying
rust-geometry-guidelines.md §6 records one fog advance over a 256×256 chunk at 29.4 ms at
Q-012 = 256, so Q-015a’s 300 ms/room-second isolation ceiling admits 10 advances per
room-second.
The next figure is derived, not measured. It applies §5.2’s LOS ratio to §6’s fog measurement.
At 5.32× an advance becomes ~156 ms and the ceiling admits ~1.9 advances per room-second —
an 81% cut. §6 states plainly that Q-015a was not raised to fit its load, because “an
isolation ceiling raised to fit its load stops being one.”
Even at the more favourable 3.85× the budget falls to ~2.7 advances per room-second. Both ends of the measured range break it, which is what makes this decidable without the fog measurement.
Decision
-
descent-geometry-c-abistays. No P/Invoke binding is removed. -
ci.ymlandDescent.Geometry.Native.targetsare unchanged. Native cross-compilation stays. Tasks 9, 10 and 11 of the brief were not performed. -
The re-evaluation trigger is a specific measurement, not a ratio improving: a wasm-vs-native measurement of the fog advancement path itself. §6 is the reason to insist — it records that the fog estimate was once wrong by 278×, and transferring a ratio measured on segment visibility to mask advancement is an assumption, not a result. A future proposal to revisit this must bring that measurement.
-
What is worth keeping is recorded rather than discarded: the C ABI’s
wasm32buildability means the frontend could dropwasm-bindgenfor the raw export surface if it ever wanted one artefact rather than two bindings. That is a separate and smaller question, and one where the 5.32× is charged to a machine that is not paying for a shared silo.
Alternatives Considered and Why Rejected
- Adopt it and raise
Q-015a. Rejected by §6’s own sentence, quoted above. This is the alternative the ruling most wanted to be available and it is the one the corpus has already refused in advance. - Adopt it for LOS only and keep the native host for fog. Coherent, and rejected: it is two hosts for one crate, which is the “kept in agreement” cost ADR-017 exists to avoid, in exchange for removing a binding that would then still exist.
- Wait for a faster wasmtime. Not an alternative — it is the trigger, and clause 3 states what evidence would fire it.
- Accept the 3.85× figure as the operative one on the grounds that dispatch dominates. Rejected: it also breaks the budget, so choosing the flattering number changes nothing except how the decision reads.
Consequences
- Nothing changes. The geometry pipeline, its CI job and its four parity arms are as they were.
core/Descent.Geometrygains a dev-dependency on its own C ABI binding so the benchmark can call both arms; nothing ships depending on it.- Part 3 of the brief is not delivered, and this ADR is the reason.
Rights-holders (ADR-079)
None. No data path changes.
Enforcement
- The decline has no enforcement point and needs none: it is the absence of a change. The control is the trigger in clause 3, and the benchmark that would answer it is committed and runnable.
descent-geometry-c-abicontinues to be built by thegeometryjob’s step 7 and byDescent.Geometry.Native.targets, both unchanged — so its removal would be a visible edit rather than a quiet one.
Verification
Descent.Sandbox.Tests: 666 assertions, 0 failures (645 before this work), run by launching the test binary directly —core/Descent.Sandbox/ARCHITECTURE.md§9 explains whydotnet testreports zero for this project.descent-wasm-core: 36, 0 failures — 32 unit and 4 that execute the realwasm32artefact through the Rustwasmtimecrate with no .NET in the picture.Descent.IntegrationTestsparity: 8, 0 failures.- The mutation sweep, six single-behaviour mutations, two initially uncaught and both repaired — recorded in the audit’s §2.9 and summarised in ADR-141.
adr_link_lint.pyread 137 rows, 0 findings before this ruling; the four rows below take it to 141.