Skip to main content
Next (unreleased) · docs c218d875 · core f63c1e69 · built 2026-10-26 22:39 UTC

v0.14.0 high-risk boundary probes

These probes record failures on core b85a6cd6 (2026-10-08). The baseline results below are historical; fixes and regression fixtures are now present in the local integration worktree, pending merge. Run each standalone source with target/debug/metel, adding --move-check for the ownership cases. Code blocks deliberately use text: accepting a known defect as a successful documentation example would misrepresent the intended behavior.

FindingTrackerLocal regression coverage
F06#1423evaluator/records/equation_remainder_method.mtl
F07#1424evaluator/aspects/bounded_method_aspect_arguments.mtl
F08#1425evaluator/type_aliases/row_parameter_aliases.mtl
F09#1426module_semantics/renamed_row_nominal/main.mtl
F10#1427typechecking/aspects/dyn_generic_method_excluded_{local,parameter,tuple}.mtl
F11#273typechecking/move_check/associated_row_result_double_move.mtl; evaluator/records/associated_row_result_move_analysis.mtl

F11 fixes concrete reconstruction failures, including E29's structural conditional-method body. It does not resolve #273's broader fail-open policy or change that tracker's v0.16.0 milestone.

F06 — Conditional dispatch on an equation-derived remainder​

Expected: assertion succeeds. Actual: T0012 at cleared.cleared(), saying the negative candidate requires a missing token label. Adding an explicit row Rest: !{token} bound also fails. Calling the method on the returned concrete remainder outside the generic definition succeeds (E29).

record Session<row R> { data: { ..R } }
extend<row R: !{token}> Session<..R> { fun cleared(self) -> i64 { 2 } }
fun strip<row R, row Rest>(session: Session<..R>) -> i64
where R = { token: String, ..Rest } {
let { token: _, ..rest } := session.data;
let cleared: Session<..Rest> := Session { data = rest };
cleared.cleared()
}
fun main() { assert(strip(Session { data = { token = "gone" } }) == 2); }

Rules: spec.types.generics.open-rows.legality-2, spec.types.generics.row-conditional-impls.legality-3.

F07 — Concrete aspect arguments are not substituted in a bounded body​

Expected: assertion succeeds. Actual: T0001, cannot unify i64 with A, at value.again(input). Concrete and dyn calls to the same default method work (E32). This probe avoids integer-literal ambiguity by annotating input.

aspect Transform<A, B> {
fun transform(&self, value: A) -> B;
fun again(&self, value: A) -> B { self.transform(value) }
}
struct Doubler {}
extend Doubler: Transform<i64, i64> {
fun transform(&self, value: i64) -> i64 { value * 2 }
}
fun generic<T: Transform<i64, i64>>(value: &T, input: i64) -> i64 {
value.again(input)
}
fun main() { assert(generic(&Doubler {}, 6) == 12); }

Rules: spec.types.generics.rigid-type-parameters.legality-2, spec.declarations.aspects.default-methods.dynamics-1.

F08 — Row-parameter alias expansion leaves a free row name​

Expected: empty row alias expands to {}. Actual: T0003, undefined row variable R at the annotated binding. Ordinary type-parameter aliases with row-valued arguments and closed row aliases work (E31).

type Row<row R> := { ..R };
fun main() { let empty: Row<{}> := {}; }

Rules: spec.declarations.type-aliases.legality-1, spec.types.generics.open-rows.legality-1.

F09 — Renaming an imported row nominal loses argument compatibility​

Expected: assertion succeeds with the same definition-site Packet identity. Actual: T0001 argument type mismatch. Importing Packet without as Renamed and constructing Packet is a passing control. No second module or collision is required to reproduce this.

model.mtl:

public record Packet<row R> { data: { ..R } }
public fun inspect<row R>(packet: &Packet<..R>) -> i64 { 1 }

main.mtl:

import model::Packet as Renamed;
import model::inspect;
fun main() { assert(inspect(&Renamed { data = {} }) == 1); }

Rules: definition-site nominal identity, spec.declarations.records.legality-1.

F10 — An excluded generic method is callable on a local dyn binding​

Expected: reject calling identity through dyn. Actual: the standalone program is accepted and executes when its generic result is discarded. Constraining the result or moving the call into a function taking dyn Mapper instead produces a T0001/T0005 involving the unresolved method parameter T, rather than enforcing the method's exclusion from dyn dispatch.

aspect Mapper {
fun identity<T>(&self, value: T) -> T;
fun ordinary(&self) -> i64;
}
struct Item {}
extend Item: Mapper {
fun identity<T>(&self, value: T) -> T { value }
fun ordinary(&self) -> i64 { 1 }
}
fun main() {
let value: dyn Mapper := Item {};
value.identity(1);
}

Rule: spec.declarations.aspects.dyn-aspect.legality-3.

F11 — Associated row result skips generic ownership checking​

Existing tracker: #273, currently v0.16.0. This is not a new fail-open policy gap, but a concrete additional reconstruction failure relevant to v0.14.0 integration.

Expected with --move-check: T0019 at the second consuming call in an uncalled generic definition. Actual: exit success, with a warning that the generic body could not be analyzed: I0001, missing nominal record identity. The valid single-call variant also warns; its concrete Holder implementation executes.

aspect Source { type Item; fun take(self) -> Item; }
fun invalid<S: Source<Item = { token: String }>>(source: S) -> {} {
let { token: _, ..rest } := source.take();
let again := source.take();
rest
}
fun main() {}

Rules: spec.ownership.values-move-by-default.legality-1, spec.declarations.aspects.associated-types.legality-1.

E29's marked body is another reconstruction warning under #273: no method mark on the symbolic nominal. Its concrete assertions pass, but that does not establish generic-body ownership checking. It adds one skipped user body to the corpus relative to the preceding tree; the F11 negative probe is kept here rather than counted as a passing negative fixture.