Repo: found by mission-world (AILANG World) while freezing the v1 DecisionPacket
(w-decision-lifecycle-freeze, item 15). Routed upstream per the mission's no-local-workarounds
rule. Third of three v0.30.0 limitations from this sprint — siblings: #712 (bare ADT field in a
record is Z3-unencodable) and #713 (ADT equality / IMP012 on the suggested fix).
Summary
The inline-test harness cannot express a test case for a function of more than one logical input:
- a multi-argument
tests [...] row does not parse; and
- a tuple-valued input does parse and is collected as a test, then fails at runtime
with no pattern matched in match expression — in both the direct tuple-pattern and the
nested-match forms.
(2) is the more expensive half: the test is collected, so it appears in the JSON with a real
location and duration and counts toward total_tests. It looks like a test that ran and
failed, not like a harness that cannot express the case.
Reproduction — AILANG v0.30.0 (commit e37b370)
Known-positive control (run first; without it none of the below is evidence)
module control
export func inc(x: int) -> int
tests [
(1, 2)
]
{
x + 1
}
$ ailang check control.ail
✓ No errors found!
$ ailang test --format json control.ail
"passed_tests": 1, "tests": [ { "name": "inc_test_1", "status": "pass" } ]
The single-int row works, so the harness is live and the two failures below are about the shape of
the input, not about a broken instrument.
(1) Multi-argument row — parse failure
module multiarg
export func addTwo(a: int, b: int) -> int
tests [
(1, 2, 3)
]
{
a + b
}
$ ailang check multiarg.ail
Error: module loading error: failed to load multiarg.ail: parse errors in multiarg.ail:
PAR_UNEXPECTED_TOKEN at multiarg.ail:6:8: expected ) to close test case
PAR_UNEXPECTED_TOKEN at multiarg.ail:6:10: expected ( to start test case
PAR_INFINITE_LOOP at multiarg.ail:6:10: parser stuck - unrecognized syntax in tests block
ailang test --format json reports a single synthetic entry {"name": "parse", "status": "fail", "location": ""}.
(2) Tuple-valued input — collected, then "no pattern matched"
Direct tuple pattern:
module tuplein
export func addPair(p: (int, int)) -> int
tests [
((1, 2), 3)
]
{
match p { (a, b) => a + b }
}
Nested-match form (match p {\n (a, b) => a + b\n}) behaves identically. Both:
"failed_tests": 1,
"tests": [ { "name": "addPair_test_1", "status": "fail",
"location": "tuplein.ail:6:3", "duration": "2.681625ms",
"error": "harness evaluation failed: harness evaluation failed:
no pattern matched in match expression" } ]
Note the doubled harness evaluation failed: prefix, and that location/duration are populated
— i.e. the case really was collected and evaluated.
Why it matters downstream
AILANG World's world/types.ail freezes five Z3-proven laws whose natural test surface is
multi-argument (timeoutOutcome(policy, escalationsRemaining, independentAuthority),
validEscalation(old, new, recordedNow, newDeadlineAt), …). Because neither form above is
available, every law is tested through a hand-written single-int case dispatcher:
func escalationCode(caseId: int) -> int
tests [ (1, 1), (2, 0), (3, 0), (4, 0) ]
{
if caseId == 1 then (if validEscalation(3, 2, 10, 20) then 1 else 0)
else ...
}
That works and is mutation-killed, but the integer codes are pure plumbing: they are not an
exported ordering, they cost a layer of indirection per law, and the mapping from caseId to
arguments is invisible to the gate. A repo that pins tests by name (ours pins 39 of them) ends
up pinning dispatcher names rather than the law under test.
What would resolve it
Either form alone would remove the dispatcher layer — (1) multi-argument rows, or (2) making the
already-collected tuple case actually destructure. (2) looks closer to landed behaviour given the
case is collected today.
If the tuple form is not intended to be supported, the smaller fix is to refuse it at collection
time with a diagnostic naming the limitation, rather than collecting it and failing later inside
the harness — a case that cannot be expressed should not be reported as a case that failed.
🤖 Reported by the AILANG World mission loop (mission-world, iteration 85)
Repo: found by
mission-world(AILANG World) while freezing the v1DecisionPacket(
w-decision-lifecycle-freeze, item 15). Routed upstream per the mission's no-local-workaroundsrule. Third of three v0.30.0 limitations from this sprint — siblings: #712 (bare ADT field in a
record is Z3-unencodable) and #713 (ADT equality /
IMP012on the suggested fix).Summary
The inline-test harness cannot express a test case for a function of more than one logical input:
tests [...]row does not parse; andwith
no pattern matched in match expression— in both the direct tuple-pattern and thenested-match forms.
(2) is the more expensive half: the test is collected, so it appears in the JSON with a real
locationanddurationand counts towardtotal_tests. It looks like a test that ran andfailed, not like a harness that cannot express the case.
Reproduction — AILANG v0.30.0 (commit
e37b370)Known-positive control (run first; without it none of the below is evidence)
The single-int row works, so the harness is live and the two failures below are about the shape of
the input, not about a broken instrument.
(1) Multi-argument row — parse failure
ailang test --format jsonreports a single synthetic entry{"name": "parse", "status": "fail", "location": ""}.(2) Tuple-valued input — collected, then "no pattern matched"
Direct tuple pattern:
Nested-match form (
match p {\n (a, b) => a + b\n}) behaves identically. Both:Note the doubled
harness evaluation failed:prefix, and thatlocation/durationare populated— i.e. the case really was collected and evaluated.
Why it matters downstream
AILANG World's
world/types.ailfreezes five Z3-proven laws whose natural test surface ismulti-argument (
timeoutOutcome(policy, escalationsRemaining, independentAuthority),validEscalation(old, new, recordedNow, newDeadlineAt), …). Because neither form above isavailable, every law is tested through a hand-written single-int case dispatcher:
That works and is mutation-killed, but the integer codes are pure plumbing: they are not an
exported ordering, they cost a layer of indirection per law, and the mapping from
caseIdtoarguments is invisible to the gate. A repo that pins tests by name (ours pins 39 of them) ends
up pinning dispatcher names rather than the law under test.
What would resolve it
Either form alone would remove the dispatcher layer — (1) multi-argument rows, or (2) making the
already-collected tuple case actually destructure. (2) looks closer to landed behaviour given the
case is collected today.
If the tuple form is not intended to be supported, the smaller fix is to refuse it at collection
time with a diagnostic naming the limitation, rather than collecting it and failing later inside
the harness — a case that cannot be expressed should not be reported as a case that failed.
🤖 Reported by the AILANG World mission loop (
mission-world, iteration 85)