062 reject identity struct mismatch

✓ Passing This code compiles and runs correctly.

Code

// NEGATIVE TEST: Identity branch decl + struct-shape proc return.
//
// `connect` declares `| ok Connection[open!]` — an identity branch whose
// payload IS a Connection. But its proc returns `.{ .ok = .{ .conn = ... } }`
// — a struct shape. The two don't match.
//
// The shape checker should reject this with a clear error pointing at the
// mismatch between the branch declaration and the constructor.
//
// Today this input segfaults the phantom semantic checker at
// findDisposalEventsForState because phantom_state slices in BindingContext
// go stale once the malformed state path is propagated through recursive
// validateContinuation frames. The compiler should never segfault on user
// input — a clean shape error is the correct outcome.

const std = @import("std");

const Connection = struct { handle: i32 };
const Transaction = struct { conn_handle: i32 };

~pub event connect { host: []const u8 }
| ok Connection[open!]
| err []const u8

~proc connect|zig {
    _ = host;
    return .{ .ok = .{ .conn = .{ .handle = 1 } } };
}

~pub event begin { conn: Connection[!open] }
| ok Transaction[active!]
| err []const u8

~proc begin|zig {
    return .{ .ok = .{ .tx = .{ .conn_handle = conn.handle } } };
}

~[!]pub event commit { tx: Transaction[!active] }
| err []const u8

~proc commit|zig {
    _ = tx;
    return .ok;
}

~event report_err { stage: []const u8, msg: []const u8 }

~proc report_err|zig {
    std.debug.print("{s} err: {s}\n", .{ stage, msg });
}

~connect(host: "localhost")
| ok c1 |> begin(conn: c1.conn)
  | ok c2 |> commit(tx: c2.tx)
    | err msg |> report_err(stage: "commit", msg: msg)
  | err msg |> report_err(stage: "begin", msg: msg)
| err msg |> report_err(stage: "connect", msg: msg)
input.kz

Error Verification

Actual Compiler Output

error[KORU030]: Phantom state mismatch: argument 'conn' has no tracked phantom state, but event requires '[!open]' (consumption). Did you mean to pass a value with state 'input:open'?
  --> phantom_semantic_check:53:0

error[KORU030]: Phantom state mismatch: argument 'tx' has no tracked phantom state, but event requires '[!active]' (consumption). Did you mean to pass a value with state 'input:active'?
  --> phantom_semantic_check:53:0

error[KORU030]: Resource 'c1' [open!] was not discharged. Call: input:begin
  --> phantom_semantic_check:53:0

❌ Compiler coordination error: Phantom semantic validation failed
error: CompilerCoordinationFailed
/Users/larsde/src/koru/tests/regression/300_ADVANCED_FEATURES/330_PHANTOM_TYPES/330_062_reject_identity_struct_mismatch/backend.zig:94:13: 0x10078900b in emit (backend)
            return error.CompilerCoordinationFailed;
            ^
/Users/larsde/src/koru/tests/regression/300_ADVANCED_FEATURES/330_PHANTOM_TYPES/330_062_reject_identity_struct_mismatch/backend.zig:184:28: 0x100789c6b in main (backend)
    const generated_code = try RuntimeEmitter.emit(compile_allocator, final_ast);
                           ^

Test Configuration

MUST_FAIL

Post-validation Script:

#!/bin/bash
# Custom validation for 330_062.
#
# The proc body declares `~event connect | ok Connection[open!]` (identity
# branch), but `~proc connect` returns `.{ .ok = .{ .conn = ... } }` — a
# struct shape that doesn't match the declared identity-branch payload type.
#
# Per koru/CLAUDE.md "Koru is emit-only with the host language," shape_checker
# does NOT introspect proc body Zig. So the diagnostic for this mismatch must
# come from downstream: when c1 is bound from the malformed return, its tracked
# phantom state is inconsistent, and the phantom checker catches it as soon as
# c1.conn is passed to `begin`.
#
# This script verifies that:
#   1. The compiler produced KORU-coded errors (didn't crash with a Zig stack
#      trace from a segfault on the malformed state propagation path).
#   2. The diagnostic identifies the bound binding's bad shape (phantom state
#      mismatch / not-discharged), proving Koru caught the mismatch downstream
#      rather than letting it propagate to Zig.
#   3. The error message is meaningful (mentions the bound name and what's
#      wrong), not just a generic panic.
#
# A regex pin in expected_patterns.txt was the old approach. It demanded the
# error mention "identity" and come from shape_checker — both unreachable under
# the emit-only principle. post.sh expresses the actual invariant instead.

set -e

[ -s backend.err ] || { echo "FAIL: backend.err is missing or empty"; exit 1; }

# Must produce a KORU error code, not a raw Zig stack trace from a crash.
if ! grep -qE 'error\[KORU[0-9]+\]' backend.err; then
    echo "FAIL: no KORU error code in backend.err — compiler may have crashed"
    cat backend.err
    exit 1
fi

# Must not contain segfault / panic / general protection markers.
if grep -qE 'general protection exception|panic:|Segmentation fault' backend.err; then
    echo "FAIL: compiler crashed instead of producing a clean diagnostic"
    cat backend.err
    exit 1
fi

# Must identify the bound-binding shape mismatch. Phantom checker catches this
# as either "no tracked phantom state" on the argument or "not discharged" on
# the bound resource — either is acceptable evidence the mismatch was caught.
if ! grep -qE 'no tracked phantom state|was not discharged' backend.err; then
    echo "FAIL: diagnostic doesn't reference the bound-shape mismatch"
    cat backend.err
    exit 1
fi

# Must reference at least one of the bindings from the test flow.
if ! grep -qE "'(c1|c2|conn|tx)'" backend.err; then
    echo "FAIL: diagnostic doesn't name any of the involved bindings"
    cat backend.err
    exit 1
fi

echo "PASS: malformed identity-branch return diagnosed downstream without segfault"
exit 0