Skip to content

0.1.0-pre17 planning umbrella: correctness, Scalus parity, and follow-up roadmap #121

Description

@satran004

Purpose

Track and prioritize the remaining open JuLC work after PR #109 merged ADR-032 into main at 4401b1a8.

This is a planning and sequencing umbrella. The 0.1.0-pre17 label identifies the planning horizon; it does not make every deferred research item a release blocker. Correctness, deterministic evaluation, and dependency order take precedence over feature count.

Status reconciliation — 2026-09-21

P4 waves 1–4 are merged: #141, #142, #144, #147, #148, #149 and #150. #112/#114/#115/#116/#117/#119/#120 are implemented within their documented bounded scopes; #113 is complete as a rejected proposal, not an implementation. PR #156 subsequently removed the compiler's pinned-cost-profile requirement without changing O9 lowering.

Correctness PRs #157, #164, #165 and #167 are also merged. PR #167 merged on 2026-09-21 at 539c3f15, automatically closing #166 as completed. The loop/switch fixes tracked here are landed. Keep this tracker open: the profile-freeze ADR/hash-stability policy, final evidence audit and release-candidate validation/publication are not established as complete. GitHub still lists pre16 as the latest release.

The per-slice measurements below are historical implementation evidence, not a fresh audit of one final release artifact. Scope exclusions and residual research are not completed merely because an implementation PR merged.

Priority order

P0 — reconcile completed roadmap state

P1 — release-facing compiler correctness and backend parity

#40 is not a prerequisite for #74 or a pre17 release blocker. New evaluation work should still avoid introducing additional shared mutable configuration.

P2 — non-blocking hardening, cleanup, and fidelity work

P3 — next compiler optimization slices

Each slice must preserve evaluation order, decode/failure timing, representation boundaries, deterministic bytes, source attribution, and cross-backend behavior. Do not replace these with untyped UPLC peepholes.

P4 — PV11 lowering completion (scope decision 2026-09-13: do before the remaining release gates)

Scope decision (maintainer, 2026-09-13). All P4 items move ahead of the evidence-audit and release-candidate gates. Rationale: every one of them lowers to PV11-only builtins under PV11_SAFE/PV11_COSTED and changes emitted bytes/hashes for eligible programs; pre17 is a preview where that churn is acceptable, and landing it now lets the next milestone freeze the default PV11 rule set so later releases change PV11 hashes only for announced correctness fixes. NONE/BASELINE retain historical bytes for these optimization slices; that is not a blanket guarantee for subsequent correctness fixes such as #161/#166, whose affected-script changes apply at all levels. Each slice follows the #110/#111 pattern: ADR → bounded implementation → semantic/failure/deterministic-hash/size-budget/Java+Truffle+Scalus evidence → independent review → node evidence where material → release-note migration line → Blaster fixture check (verification/blaster/fixtures).

Original execution order (2026-09-13; completion status below):

  1. Wave 1 — Case decisions, one ADR: ADR-032 follow-up O5: define safe Integer Case source semantics #112 (O5 Integer Case on compiler-generated sealed dispatch; fires on every sealed switch, largest hash mover, smallest code) and ADR-032 follow-up O6: add typed Unit sequencing before Case lowering #113 (O6 Unit Case; JuLC emits no ChooseUnit, so the real candidate is [(λ_. rest) e] → case e [rest], which needs a typed Unit node — expected outcome: reject). Implement whichever is accepted.
  2. Wave 2 — pure typed lowering, independent, may run in parallel: ADR-032 follow-up O8: implement typed native Value conversion motion #114 (O8 Value conversion motion; O7 invariants already exist) and ADR-032 follow-up O9: implement costed List-to-Array promotion gate #115 (O9 cost-directed list-to-array promotion with use/escape and failure-equivalence proof).
  3. Wave 3 — sharing: ADR-032 follow-up O15: add minimal representation-aware let sharing #120 (O15 representation-aware let sharing), only after ADR-032 follow-up O8: implement typed native Value conversion motion #114 and ADR-032 follow-up O9: implement costed List-to-Array promotion gate #115 establish the consumer invariants; must not become general CSE.
  4. Wave 4 — new typed surfaces (additive; cannot move hashes of programs compiled today): ADR-032 follow-up O14: define native Value literals and safe folding #119 (O14 native Value literals and canonical folding, builds on the typed Value region), then ADR-032 follow-up O10: design typed native Array literals and safe folds #116 (O10 typed Array literal producer and folds), then ADR-032 follow-up O11: define typed BLS values and implement explicit MSM #117 (O11 typed BLS types and explicit MSM API, largest API ADR, fewest users).
  5. Then: profile-freeze ADR (default PV11_SAFE rule set frozen; new rules only in a new opt-in profile) and the hash-stability policy doc, followed by the evidence audit, release-candidate validation, and the pre17 publish.

Items:

  • ADR-032 follow-up O5: define safe Integer Case source semantics #112 — wave 1: merged PR feat(compiler): PV11 integer Case dispatch for sealed switches (ADR-041, #112) and O6 decision (#113) #141 (2026-09-14, ADR-041); issue closed. Sealed dispatch with ≥2 constructors lowers to one PV11 integer Case at PV11_SAFE (pv11.o5.case-integer); failure contract pinned; VM integer-Case range hardening; decompiler recognizer; goldens from c2142e92; on-chain Haskell-exact; external examples 420/0.
  • ADR-032 follow-up O6: add typed Unit sequencing before Case lowering #113 — wave 1: proposal rejected by ADR-041 (merged PR feat(compiler): PV11 integer Case dispatch for sealed switches (ADR-041, #112) and O6 decision (#113) #141, 2026-09-14); issue closed. No Unit Case implementation was added. — JuLC emits no ChooseUnit; census of 58 shipped validators shows no typed-unit statement population worth a new PIR surface. Decision task complete; this is not an implemented optimization.
  • ADR-032 follow-up O8: implement typed native Value conversion motion #114 — wave 2: merged PR feat(compiler): share repeated native Value conversions at the safe profile (ADR-042, O8) #142 (2026-09-15, ADR-042); issue closed. Strict-prefix sharing of a repeated unValueData conversion of one variable at PV11_SAFE (pv11.o8.value-sharing): shared only when the conversion is already the first non-trivial evaluation of its scope, so results, traces and failure text are unchanged; per-iteration loop conversions hoist (−1.5M CPU on a 3-iteration fixture); loss bounded at 48,000 CPU per binding on paths that never reach a second occurrence; ValueData sinking/cancellation and Data-side ValuesLib sharing recorded as out of scope; no shipped example changes hash; goldens from 5e32bbcd.
  • ADR-032 follow-up O9: implement costed List-to-Array promotion gate #115 — wave 2: merged PR feat(compiler): promote repeatedly indexed lists to PV11 arrays at the costed profile (ADR-043, O9, #115) #144 (2026-09-15, ADR-043); issue closed. Costed-only list-to-array promotion (pv11.o9.list-to-array, fires only at PV11_COSTED): a JulcList variable indexed at two or more get sites, or at a site inside a loop/recursive helper/inlined callback, is converted once with ListToArray and its sites become IndexArray, bound at the innermost evaluated-once sub-term (two-site output byte-identical to a hand-written toArray()). Failure contract: out-of-range index fails at IndexArray (same point, smaller budget, different off-chain text), observable from validators through the strict boundary, pinned on Java/Truffle/Scalus. Break-even measured from the pinned profile and reproduced exactly (two sites at 0/1 gain up to 44 elements; WingRiders-shaped loop with 16 requests 349.6M → 81.9M CPU). Only bindings proven to hold a list are promoted (proven-name environment; casts and every alias of them, including the loop lowering's let xs = xs, are never promoted; callback parameters never trusted); the residual through helper parameters/returns and loop state is pinned as a documented costed-only divergence (CAST_HELPER, CAST_LOOP_STATE) — retained as the merged costed-only contract, not a claim that unchecked casts preserve safe-profile behavior. DropList get lowering measured and filed as Decide: lower JulcList.get(i) to a guarded DropList form instead of the recursive traversal (measured under ADR-043 / #115) #143. Goldens from 940dc65b (20 fixtures × 4 levels); default level byte-identical everywhere; external examples at pv11-costed byte-identical (no promotable shape in the compiled corpus; the real WingRiders pool validator compiled with the CLI drops 1,943 → 1,699 bytes).
  • ADR-032 follow-up O10: design typed native Array literals and safe folds #116 — wave 4: merged PR feat(compiler): typed array literals and literal folding at the safe profile (ADR-046, O10) #149 (2026-09-16, ADR-046); issue closed. JulcArray.of(a, b, ...) is the typed array literal: lowered to ListToArray over the JulcList.of list literal, with the Data-encoded element representation JulcArray<T> already has (a native Value element is JULC0041); PV11 only. ArraySemantics in julc-core pins the three array builtins (both IndexArray failure texts) and the VM delegates to it. ArrayLiteralFoldPass (rule pv11.o10.array-literal-fold, PV11_SAFE, switchable; the ADR-045 machinery is now the abstract LiteralFoldPass) folds ListToArray of a list literal to an array constant, LengthOfArray and literal-index IndexArray over it to their results, and the get decode only over elements it produced (never a pre-existing Data constant); out-of-range literal indexes stay and fail with the builtin's text; MultiIndexArray is never emitted. Additive: no existing program contains an array constant or converts a list literal to an array; every golden suite, the Blaster lock and all 41 example validators are byte-identical at every level. 19 fixtures × 4 levels × rule off/on on three VMs; e.g. JulcArray.of(1, 2, 3).get(1) 42 → 6 bytes, 1,238,594 → 16,100 CPU; a runtime-indexed table 49 → 34 bytes, 1,435,338 → 625,598 CPU on every path. Two review agents, no blocking finding (fixes: objective on the Bool fold, requirement key under the qualified class name, stronger failure/budget assertions, var divergence pinned). Open: a native array integer representation behind its own marker type, sharing repeated length/access on one array, map elements and negated literals not read as literals, on-chain gate for an embedded array constant.
  • ADR-032 follow-up O11: define typed BLS values and implement explicit MSM #117 — wave 4: merged PR feat: typed BLS12-381 values, native scalar/point lists and explicit multi-scalar multiplication (ADR-047, O11, #117) #150 (2026-09-17, ADR-047); issue closed. Typed BLS12-381 surface: JulcG1/JulcG2/JulcMlResult and the native lists JulcScalars/JulcG1Points/JulcG2Points (opaque PIR types under the O7 isolation rules, JULC0041/JULC0042); every Builtins.bls12_381_* and BlsLib method retyped (compress the only way to bytes, uncompress the only way back); the registry's per-builtin signature table rejects a wrong group, a byte string, Data or a Data list at compile time. Explicit g1MultiScalarMul/g2MultiScalarMul over native lists built only by the intrinsics scalars/g1Points/g2Points and the converters scalarsFromList/g1PointsFromCompressed/g2PointsFromCompressed (no Data wrapper possible); MSM semantics pinned on three VMs (all scalars validated first at 512 bytes, zip to the shorter list, empty sum is the identity). No pass, no fusion. Additive: no corpus/golden/Blaster program uses the BLS surface; a var-style BLS program compiles to identical bytes under both APIs; all 41 example validators byte-identical at every level. Crossover on the pinned profile: MSM smaller from three points, cheaper in CPU from seven (net of the shared point hashing: about 322M to enter + 25M per point versus 77M per point). Review fixes: element typing of scalars and the converters; same-class helper arguments and return expressions now under the native check. Migration: byte[] locals of BLS values are now JULC0041 (var unaffected). Open: literal-scalar bound check, chain fusion (costed), inverse converter, on-chain gate.
  • ADR-032 follow-up O14: define native Value literals and safe folding #119 — wave 4: merged PR feat(compiler): typed native Value literals and literal folding at the safe profile (ADR-045, O14) #148 (2026-09-16, ADR-045); issue closed. Typed native Value literal producers as Builtins intrinsics (emptyValue() = a UPLC Value constant; singletonValue(policyId, tokenName, quantity) and lovelaceValue(quantity) = insertCoin into it, with the builtin's zero/negative/key/range rules), gated by the PV11 Value-constant capability. NativeValueSemantics in julc-core pins the seven Value builtins (exact failure texts) and the VM now delegates to it, so the compiler fold and the runtime share one implementation. ValueLiteralFoldPass (rule pv11.o14.value-literal-fold, PV11_SAFE, switchable) folds a saturated all-literal call of any Value builtin (bare, through a NativeValueLib wrapper, or over once-bound literal locals) into its result when the semantics succeed and the literal's FLAT encoding is not larger than the call's; rejected calls stay as written (same failure text on Java/Truffle/Scalus); no algebraic identities. Additive: no existing program contains a Value literal, so every golden suite, the Blaster lock and all 41 example validators are byte-identical at every level (the producers are intrinsics precisely so that no dead library binding is added). 29 fixtures × 4 levels × rule off/on on three VMs; e.g. a literal lookup 40 → 7 bytes, 883,863 → 16,100 CPU. Two review agents found one blocking hole (a wrapper with an unused parameter dropped that argument unexamined), fixed with probes; the objective is measured in FLAT bits against the call site. Open: CPU-aware objective for fromData of large Data literals, toData(emptyValue()) stays a call by two bytes, negated literals (new BigInteger("-5")), folding the empty-Value idiom (hash-moving), NativeValueLib spelling once bindings are pruned, on-chain gate for an embedded Value constant.
  • ADR-032 follow-up O15: add minimal representation-aware let sharing #120 — wave 3: merged PR feat(compiler): share repeated record field projections at the safe profile (ADR-044, O15) #147 (2026-09-15, ADR-044); issue closed. The ADR-042 sharing pass generalised to three unit classes under the same leading rule: record field projection chains (D(headList(tailList^k(sndPair(unConstrData(x))))), one wrapDecode arm, matched outermost-first, a matched unit a leaf for collector and rewriter alike) and the fields prefix (sndPair(unConstrData(x))) join the native Value conversion; rounds run chains to a fixed point (chain on chain), then the prefix, then Value; units inside transitively dead bindings (uncalled library methods) are skipped; the body of a single recursive binding of a lambda can lead (the per-site get lowering). Rule pv11.o15.projection-sharing at PV11_SAFE, failure-text neutral on Java/Truffle/Scalus; exact cost model pinned (after = before + (2+k)·16,000 − (k−1)·unit; loss bound 48,000 CPU per binding). Each PIR rule is now individually switchable (CompilerOptions.disableOptimizationRule, JULC0043 for unknown ids), as ADR-032 follow-up O15: add minimal representation-aware let sharing #120 requires. Hashes move at the default level: 26 of 41 example validators change hash or size (the rule's trigger shape is in most validators); the hash-stability policy doc gets its precedent here. Goldens from 1fd98c93 (24 fixtures × 4 levels, self-verifying with the rule off); O8 fixtures byte-identical with every rule on; costed-only handoff: O9 now promotes x.items().get(i) sites because the list is bound first. Two review agents + advisor, all should-fixes folded. Open: units that do not lead, ValuesLib repeats, provenance from the final artifact, compareTo receiver duplication (compareTo lowering evaluates a non-trivial receiver and argument twice #146), loop self-alias, plugin/CLI switch exposure.

Post-implementation configuration correction:

Dependency/scope notes:

P5 — verification research and roadmap trackers

Merged compiler slice: #110 / PR #123

  • Typed PIR ListMatch lowers eligible for-each traversals to guarded PV11 List Case. The original NullList guard preserves malformed-runtime-value failures and element decoding remains strict.
  • Enabled in default PV11_SAFE and in PV11_COSTED for the explicit PV11 target. NONE/BASELINE retain historical bytes. Recompilation with the updated safe profile can change script bytes/hashes; deployed scripts and ledger Data encodings are unchanged.
  • Independent Claude review reported approval with notes. Follow-ups restore decompiler loop classification, add explicit serialized List Case branch-order/binding/laziness/failure tests on Java/Truffle and applicable Scalus, and reconcile ADR-032/034 bookkeeping.
  • Focused tests, affected modules, full repository build and documentation build passed locally. Follow-up suites passed all 96 decompiler and 21 benchmark tests. Fresh VM/conformance results are recorded separately in the implementation evidence; the 999-case builtin/example inventory does not itself cover term-level List Case. Scalus language-level checks do not imply ledger certification.
  • Evidence: ADR-034, validation and review, size/budget/hash measurements.
  • Broader traversal families and the optional one-branch List Case size optimization remain deferred. The completed checkbox above records the merged bounded slice, not all possible O3 traversal optimizations.

Merged cleanup and decision: #22 / #118 / PR #124

Merged compiler slice: #111 / PR #126

  • Merged 2026-09-06 at 8af52387; ADR-032 follow-up O4: implement typed Pair destructuring analysis #111 is closed. Typed PIR PairMatch replaces direct FstPair/SndPair uses of the same once-bound UnConstrData result only when the local-use and native representation proof succeeds.
  • Enabled under default PV11_SAFE and PV11_COSTED for the exact PV11 target. Producer strictness, decoding/trace/failure order and lexical scope are preserved. Aliases, escaping pairs, one-sided uses, arbitrary pair producers and map traversal are excluded. Sealed-interface switch decomposition is tracked separately in ADR-036 follow-up O4: native Pair Case for sealed-interface switch dispatch #125.
  • NONE/BASELINE preserve historical bytes. Safe-profile recompilation, including eligible caller-supplied PIR through compilePirToProgram, can change bytes/hashes; deployed scripts and ledger Data encoding are unchanged.
  • Independent Claude review supplied by the maintainer approved with notes, independently reproduced all 18 pre-change artifacts and measured costs/hashes, and reported fresh passing focused/downstream suites plus adversarial scope probes. Required documentation notes were addressed before merge.
  • Local full build and documentation build passed. Fresh repository test run: 10,173 passed, 531 existing/profile-inapplicable skips, zero failures. Dedicated pairCaseTest: 13 passed; CI check/build includes this task. Java and Truffle each passed all 999 PV11 conformance cases; explicit Pair Case order/strictness tests add direct lowering coverage.
  • Published implementation 002b2ea7 to Maven local as 0.1.0-pre17-002b2ea-SNAPSHOT; the sibling julc-examples resolved the exact plugin/compiler/AP/runtime artifacts. External results: 409 passed, 11 pre-existing skips, including all 54 transaction integration tests on the available PV11 Yaci node. Escrow Java and backend-evaluation budgets matched exactly; inspected generated artifacts contain the new pair lowering. The original report called that endpoint Ogmios/Haskell, but the ADR-032 follow-up O3: implement typed List case lowering #110 follow-up found that the same API can use Scalus. Historical evaluator identity needs configuration evidence; it is not retroactively established by the new run.
  • External packaging initially hit the checkout's existing missing src/main/plutus directory, reproduced with its original plugin. An isolated compileJulc fixture made packaging pass, producing the pinned 323-byte script/hash; the examples build file was preserved.
  • Evidence: ADR-036, Maven-local replay script. Representative isolated O4 FLAT sizes improve 360→323, 144→130 and 270→247 bytes, with non-increasing measured CPU/memory on success and failure paths.

Completed fidelity audit: #55

Merged decoder fidelity: PR #127

  • Merged 2026-09-06 at 99a69705. Map-only byte decoding preserves entry order and duplicates recursively, including FLAT Data constants; compiler lowering and encoding are unchanged.
  • Local fresh full build: 10,283 passed, 531 existing/profile-inapplicable skips, zero failures/errors. All 999 PV11 conformance cases passed on Java and Truffle; 48 cross-VM regression cases passed. Documentation build passed; all 41 existing external example artifacts retained exact FLAT bytes on read/re-encode.
  • The external artifact replay was not a fresh transaction-suite or Yaci run. Existing general CBOR nesting/size limitations remain outside this correction.
  • Evidence: ADR-037, validation.

Completed #110 node gate / merged PR #128

  • Completed 2026-09-06 on test/110-list-case-onchain-evidence, commit e8f84ec3 (PR Complete List Case on-chain validation with direct Haskell budgets #128), based on merged main 99a69705. No compiler, VM or encoder changes were needed; the bounded lowering remains the implementation from PR Implement guarded PV11 List-case lowering for for-each loops #123.
  • New dedicated opt-in :julc-e2e-tests:listCaseOnChainTest -Pe2e checks serialized guarded List Case shape, rule provenance, the default safe profile and the pinned live PV11 cost array. It always reruns and fails on skipped/missing profile coverage. Existing backend-only E2E setup is preserved.
  • Both BASELINE and PV11_SAFE passed empty, singleton, multiple-element and early-break scenarios: eight confirmed phase-2-valid spends, with exact Java/direct-Haskell budget agreement for every case. Eight wrong-total/malformed variants were rejected by Java, the backend and Haskell with the expected script failure causes; invalid variants were not submitted.
  • Provenance correction: Yaci Store's evaluation endpoint was using Scalus, despite an Ogmios-branded response. The test therefore invokes Haskell cardano-cli 11.0.0.0 directly against the running cardano-node 11.0.1 socket. The original endpoint remains an additional comparison, not the Haskell oracle. No devnet restart/reset was performed.
  • Complete-profile sizes: BASELINE 329 bytes, PV11_SAFE 285 bytes. Multi-element [2,3,4] CPU: 15,267,988 → 10,352,422; memory: 61,287 → 44,372. These include other safe rules and are not O3-only gains.
  • Fresh full build: 10,283 passed, 531 existing/profile-inapplicable skips, zero failures/errors; all 218 tasks executed. All 999 PV11 conformance cases passed on Java and Truffle. Focused O3/semantic/decompiler tests, existing backend-only E2E smoke tests and documentation build also passed.
  • Full commands, failure scope, script hashes and transaction IDs are recorded in adr/evidence/034-list-case-onchain.md and its transaction CSV in merged PR Complete List Case on-chain validation with direct Haskell budgets #128. PR Complete List Case on-chain validation with direct Haskell budgets #128 merged on 2026-09-06 at f27f1ca0; ADR-032 follow-up O3: implement typed List case lowering #110 is closed. The bounded lowering and its node evidence are complete. Broader traversal families remain deferred.

Merged compiler slice: #125 / PR #129

  • Merged 2026-09-06; ADR-036 follow-up O4: native Pair Case for sealed-interface switch dispatch #125 is closed. ADR-038 extends typed Pair Case to compiler-generated sealed-interface switch decomposition. The existing integer tag dispatch, strict scrutinee evaluation, field decoding, lexical bindings, trace order and failure behavior are preserved. Public PIR referring to the historical pair binder retains the legacy expansion.
  • Enabled under default PV11_SAFE and PV11_COSTED for the exact PV11 target. NONE/BASELINE retain historical bytes; safe-profile recompilation can change script bytes/hashes. Deployed scripts and ledger Data encoding are unchanged.
  • Independent Claude review approved with no correctness findings, reproduced all 18 historical fixture rows and measured savings, and exercised nine additional adversarial PIR shapes across Java, Truffle and Scalus. Review-status documentation was reconciled before merge.
  • Author fresh full build: 10,292 passed, 531 existing/profile-inapplicable skips, zero failures/errors; all 218 tasks executed. Java and Truffle each passed all 999 PV11 conformance cases. Dedicated pairCaseTest passed 21 tests; documentation build passed. These full-build/conformance/docs results were author-run, while the reviewer freshly reran the affected downstream suites.
  • Both author and reviewer tested the external julc-examples against freshly published Maven-local snapshots: 420 cases, 409 passed, 11 pre-existing skips, including all 54 transaction integration tests. The examples build file was preserved.
  • The dedicated switch on-chain gate passed both BASELINE and PV11_SAFE: four confirmed valid spends and eight invalid redeemer rejections, with exact Java/backend/direct-Haskell budget agreement. Direct Haskell cardano-cli is the node oracle; the backend endpoint is Scalus. Safe scripts contain the new pair-case sites. Scalus language-level agreement does not imply ledger certification.
  • Representative isolated savings are 10 FLAT bytes and 491,887 CPU / 1,364 memory per executed switch pair site. Complete-profile node comparisons also include other safe rules and must not be attributed solely to this extension.
  • Evidence: ADR-038, validation and node evidence. Decompiler recognition was subsequently completed in Decompiler: recognize native Pair Case in sealed-interface switch dispatch #130 / merged PR Recover native Pair Case constructor dispatch in the decompiler (#130) #131; the documented pre-existing nested-yield frontend finding is outside this compiler slice.

Merged decompiler follow-up: #130 / PR #131

  • Merged into main on 2026-09-07 at 9eb685c5; Decompiler: recognize native Pair Case in sealed-interface switch dispatch #130 is closed. ADR-039 adds conservative recovery of native Pair Case and verified legacy constructor dispatch, preserving explicit pair/tag/fields bindings, ordered BigInteger tag branches and the residual fallback.
  • Scope resolution preserves captures through nested matches, closures and FLAT round-trips. Malformed or ambiguous shapes retain generic recovery; singleton decomposition does not invent a tag-zero check. Analysis traversal and naming/type inference support the new HIR node.
  • Compatibility: external exhaustive HIR visitors must handle the new DataMatch variant. Decompiled names/output can change; readable reconstruction does not promise recompilable Java or original-schema recovery. Compiler-generated script bytes/costs and ledger encoding are unchanged.
  • Implementation validation recorded in the PR: fresh full build executed all 218 tasks, with 10,301 passed, 531 existing/profile skips and zero failures/errors. Decompiler: 105 passed; analysis: 82 passed and 2 existing skips. Java and Truffle each passed all 999 PV11 conformance cases. Documentation build passed (32 pages).
  • Bounded original/reconstructed evaluation tests compare values, failure text and traces on Java and language-only Scalus before/after serialization, including malformed shapes, nested captures, unknown-tag fallback and strict unused-field failures. This is bounded regression evidence, not certification of all decompiler heuristics. No additional node run or Maven-local publication was needed for this decompiler/analysis change.
  • Evidence: ADR-039, validation.

Merged serialization correctness and prevention: PRs #133–#135

Conditional nested yield correctness — #137 / PR #138 (merged 4cf87e70)

Status 2026-09-13. Filed as #137 (the yield sibling of #79) and fixed on PR #138, merged 2026-09-13 at 4cf87e70 (final head fa427a90, build + both Blaster verify checks green). Root cause: the #79 early-exit lowering only recognized ReturnStmt, so a yield inside an if took the legacy Let("_if", ifExpr, rest) path and the trailing yield ran unconditionally. The fix widens the predicate to yields owned by the enclosing switch expression (stopping at nested SwitchExpr boundaries as it already did at lambdas), rejects yield inside for-each/while bodies like return, and diagnoses a case block whose paths do not all end in a yield — including blocks ending in a trailing loop, closed after Codex review. Frontend lowering, so recompiling sources with the affected shape changes bytes/hashes under every profile including NONE/BASELINE; other sources keep their bytes (all existing goldens unchanged). No shipped stdlib/example uses the shape; the two Blaster controlled-mint verification fixtures do, and their locked artifacts, counterexample binding and manifest lock hash were refreshed (other five artifacts byte-identical, local verify.sh and the exact CI runner re-establish SMT-VALID; recorded in ADR-003 and the Blaster README). Evidence on the PR: 15 new tests (reproducer across NONE/BASELINE/PV11_SAFE on Java, Scalus, Truffle), fresh :julc-compiler:test 1,617/0, pairCaseTest 22/0, stdlib 403/0 (1 pre-existing skip), testkit 191/0, examples 81/0, docs build 32 pages. Release note added. No ADR, following the #79 precedent. Residuals: SwitchExpr boundary verified by reasoning plus unchanged fixtures rather than a dedicated test; trace order pinned via failure timing only.

The original audit record follows.

Fresh release audit, 2026-09-08, main fe4dbb9d: confirmed the pre-existing frontend finding recorded in ADR-038 evidence. No dedicated open issue for this finding was found in the audit.

Minimal source shape:

sealed interface Action permits Check, Stop {}
record Check(BigInteger n) implements Action {}
record Stop() implements Action {}

static boolean check(Action action) {
    return switch (action) {
        case Check c -> {
            if (c.n().signum() > 0) { yield false; }
            yield true;
        }
        case Stop s -> false;
    };
}

Compile check with JulcCompiler.compileMethod, then evaluate with argument PlutusData.constr(0, PlutusData.integer(1)) using Java and the returned compiler target. Expected BoolConst(false); actual BoolConst(true). Both BASELINE and PV11_SAFE compiled without errors and failed the semantic assertion. The isolated audit probe ran with ./gradlew :julc-compiler:test --tests '*ReleaseNestedYieldProbeTest' -PskipSigning=true: two cases, two assertion failures, zero skips. The probe is local investigation evidence, not a merged regression test. No production files were modified.

This can turn an intended rejection into acceptance when the affected source shape is used in validation logic. It reproduces under BASELINE and is not attributable to the new Pair Case optimization. The exact control-flow repair still requires design/review.

Recommended bounded completion criteria:

  • Establish a focused frontend issue/design: yield exits its owning switch expression, while method returns, loop exits and inner switches retain their own scopes.
  • Either implement correct supported lowering or reject the unsupported nested-yield shape with an actionable diagnostic; never silently accept the wrong behavior.
  • Cover true/false paths, fallthrough, if/else, deeper nesting, inner switch ownership, traces and failure timing; evaluate generated programs on Java/Truffle and applicable Scalus under baseline and safe profiles.
  • Check nearby early-return/loop/switch regressions, deterministic output and affected artifact/hash migration. Add release notes and evidence before considering the release gate satisfied.

Remaining follow-ups — reconciled 2026-09-21

  1. Resolve the confirmed nested-yield correctness finding above. Done on PR fix(compiler): preserve conditional yield control flow in switch case blocks (#137) #138 (Conditional nested yield in switch-expression arm is silently discarded (yield sibling of #79) #137), merged 4cf87e70. That was the 2026-09-13 audit state; later Loop lowering: an update to a loop-body local inside an if branch is silently lost #155/Compiler: loop reassignment of switch case-pattern variable leaves stale field reads #162/Compiler: if outside a loop body silently loses nested-loop accumulator updates #161 fixes and Switch case-pattern fields omit Bool and String Data decoding #166 (PR Fix Bool and String decoding in switch-pattern fields (#166) #167) are now merged.
  2. Close the release-note and evidence gap — done, PR docs: serialiseData force-fix release note and re-enabled stdlib test (#121 follow-up) #139 merged 2026-09-13 at cf9dca0a. Adds the fix(compiler): emit serialiseData without a force wrapper #133 serialiseData force-fix release note with its affected-script hash migration and re-enables the stale @Disabled ByteStringLibTest serialiseData case, strengthened to the exact CBOR (0x18 0x2a); fresh :julc-stdlib:test 403/0/0 with zero skips, docs build 32 pages.
  3. Reconcile and merge the distinct remaining [vm-scalus] Scalus backend serialiseData(map) sorts + dedups, diverging from the node (preserve order + dups) #55 coverage from 8ed2a885 — done, PR test(benchmark): merge reconciled #55 map serialization fidelity coverage #140 merged c2142e92; see the P2 entry above for what was kept and dropped. No PR exists for that branch. PR Preserve duplicate map entries in Plutus Data CBOR and FLAT decoding #127 already covers serialized map literal fidelity and equality with supplied arguments; the old branch adds direct argument serialization, adapter identity and default-Scalus paths. Preserve useful missing coverage and update its stale decoder-limitation comment instead of assuming all 80 cases are new.
  4. Complete the P4 PV11 lowering waves. Done via PRs feat(compiler): PV11 integer Case dispatch for sealed switches (ADR-041, #112) and O6 decision (#113) #141/feat(compiler): share repeated native Value conversions at the safe profile (ADR-042, O8) #142/feat(compiler): promote repeatedly indexed lists to PV11 arrays at the costed profile (ADR-043, O9, #115) #144/feat(compiler): share repeated record field projections at the safe profile (ADR-044, O15) #147/feat(compiler): typed native Value literals and literal folding at the safe profile (ADR-045, O14) #148/feat(compiler): typed array literals and literal folding at the safe profile (ADR-046, O10) #149/feat: typed BLS12-381 values, native scalar/point lists and explicit multi-scalar multiplication (ADR-047, O11, #117) #150. The profile-freeze ADR and hash-stability policy doc remain pending; Decouple compiler optimization from pinned cost profiles #156's cost-profile cleanup does not replace them.
  5. Finish the per-change evidence audit and final release-candidate build/documentation/external-examples/Yaci validation, then publish pre17. The latest published release remains pre16; the development snapshot is not a pre17 release.

#40 remains a non-blocking hardening candidate for shared evaluator consumers. The accepted #112–#120 slices are complete, with #113 explicitly rejected and #118 already resolved without a new rule. Residual research such as #143/#145/#146/#151, and the #163 documentation task, remains separate and open. Scalus certification remains blocked on the documented ADR-033 divergences; #55/#135 compatibility results do not remove those blockers. These priorities do not promote deferred research or Scalus certification into mandatory pre17 scope.

Proposed pre17 release gate

Required before calling this plan complete:

Except for the explicitly selected P4/release gates above, P2 through P5 residuals are ordered follow-up candidates, not automatic release blockers. #40 remains explicitly non-blocking for this planning horizon. Moving one into the release gate requires an explicit scope decision recorded on this tracker.

Planning invariants

  • Do not equate successful compilation or green tests with semantic preservation.
  • Do not change evaluation order, strictness, failure behavior, representation, or validator boundaries without explicit tests and documentation.
  • Keep compiler legality separate from optimization profitability.
  • Preserve deterministic generated artifacts for identical inputs and configuration.
  • Record script-byte/hash migration for every enabled lowering.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions