Skip to content

refactor(adr7): extract the per-boundary call-lowering seam (path-H inc 2)#360

Merged
avrabe merged 2 commits into
mainfrom
refactor/adr7-call-lowering-seam
Jul 18, 2026
Merged

refactor(adr7): extract the per-boundary call-lowering seam (path-H inc 2)#360
avrabe merged 2 commits into
mainfrom
refactor/adr7-call-lowering-seam

Conversation

@avrabe

@avrabe avrabe commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Executes ADR-7 path-H, increment 2 (#354): lift the "how is this
cross-component call boundary lowered"
decision out of
FactStyleGenerator::generate_adapter into a dedicated adapter::call_lowering
module. The call-lowering counterpart of the address-strategy seam (#359, inc 1).

What

One deliberate behavior change: a silent-corruption backstop (#361)

The extraction is otherwise behavior-preserving. But the auto-Mythos delta-pass
surfaced a real, pre-existing latent bug that the original inline dispatch
carried: a sync call that needs transcoding but does not cross memory
(needs_transcoding && !crosses_memory — e.g. --memory shared fusion of a
UTF-8 caller and a UTF-16 callee) routed to Direct, a shim that does not
transcode → string bytes delivered verbatim in the wrong encoding, silently.

meld already fails loud on the identical async case
(guard_async_cross_encoding_strings, fact.rs:12139, #272). This PR adds the
sync twin: resolve_call_lowering_plan hard-fails with AdapterGeneration
rather than emit a silently mis-transcoding adapter — the #351/#352/#355
silent-corruption-vs-loud-failure choice, applied consistently. Actually
supporting same-memory transcoding is tracked in #361.

The guard fires on no existing fixture (the path was silently latent); full
meld-core suite green (0 failures), fmt + clippy clean.

Async is unaffected

Async boundaries are dispatched before generate_adapter (the
is_async_lift branch in generate), so they never flow through this seam; the
AdapterClass::Async match arm is unreachable! and documents that.

Why this is the seam ADR-7 wants

Future per-boundary ABI strategies — symmetric ABI (cpetig/Aptiv) and
static-base PIC (#353) — and same-memory transcoding (#361) become new
inputs/outputs of resolve_call_lowering_plan rather than new branches threaded
through the adapter hot path.

Tier-5 (adapter/fact.rs + new fusion-critical module) → Mythos delta-pass
below.

Refs #354 (ADR-7), #361, #272

…nc 2)

Executes ADR-7 path-H, increment 2: lift the "how is this cross-component
call boundary lowered" decision out of `FactStyleGenerator::generate_adapter`
into a dedicated `adapter::call_lowering` module — behavior-preservingly.

The three-way class dispatch (`crosses_memory` / `needs_transcoding` →
Direct / MemoryCopy / Transcode) and the #304 inline-eligibility guard are
decided together in `resolve_call_lowering_plan`, because they are coupled:
an adapter may be inlined only when its class is Direct AND it carries no
resource conversions AND no post-return, and that guard must stay a superset
of `generate_direct_adapter`'s pure-trampoline branch. Computing both in one
pure function makes the coupling explicit and turns the generator's call site
into a lookup instead of a re-derivation.

Async boundaries are unaffected — they are dispatched before generate_adapter
(the is_async_lift branch in `generate`), so they never flow through the seam;
the match arm is `unreachable!` documenting that.

Future per-boundary ABI strategies (symmetric ABI, static-base PIC / #353)
become new inputs to `resolve_call_lowering_plan` rather than new branches
threaded through the adapter hot path — mirroring the address-strategy seam
(#359, inc 1).

- adapter/call_lowering.rs: BoundaryFacts + CallLoweringPlan +
  resolve_call_lowering_plan; 8 unit tests covering the full class +
  inline-eligibility truth table.
- adapter/fact.rs: generate_adapter now resolves the plan, matches on the
  class for body generation, and reads inline_eligible for the #304 bypass.
- adapter/mod.rs: register the module.

No behavior change: full meld-core suite (481 lib + all integration incl.
wit_bindgen_runtime, runtime_intra_adapter, transcoding, #304 inline) green;
fmt + clippy clean.

Tier-5 (adapter/fact.rs + new fusion-critical module) -> Mythos discover pass
to follow.

Refs #354 (ADR-7)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Mythos delta-pass required

This PR modifies one or more Tier-5 source files (per
scripts/mythos/rank.md):

meld-core/src/adapter/call_lowering.rs
meld-core/src/adapter/fact.rs
meld-core/src/adapter/mod.rs

Before merge, run the Mythos discover protocol on the
modified Tier-5 files:

  1. Follow scripts/mythos/discover.md
    — one fresh agent session per touched Tier-5 file.
  2. For each finding, the agent must produce both a Kani
    harness and a failing PoC test (per the protocol's
    "if you cannot produce both, do not report" rule).
  3. Attach a comment on this PR with either the findings
    (formatted per discover.md's output schema) or
    NO FINDINGS.
  4. Add the mythos-pass-done label to this PR.

Why this gate exists: LS-A-10
(CABI alignment padding in async-lift retptr writeback) was
found by the v0.8.0 pre-release Mythos pass — but it had
lived in the callback emitter since #128, across six
releases. A PR-time gate would have caught it at review
time instead of at the release boundary.

The gate check on this PR will pass once the label is
applied.

@avrabe

avrabe commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Mythos discover pass — NO FINDINGS

This PR is a single logical extraction (the per-boundary call-lowering decision) spanning the three touched Tier-5 files, so it was reviewed as one adversarial unit with the highest prior on the refactor failure mode: does the extracted resolve_call_lowering_plan diverge from the original inline dispatch for any input? A divergence would silently mis-lower a boundary (wrong AdapterClass = wrong ABI/semantic drift, or wrong inline_eligible = skipped post-return / resource cleanup).

meld-core/src/adapter/call_lowering.rs (new) — NO FINDINGS

meld-core/src/adapter/fact.rs (wiring) — NO FINDINGS

Term-by-term equivalence vs the removed inline code (old fact.rs:5297-5332):

  • Class selection — identical predicates, identical order, identical else-fallthrough: crosses_memory && needs_transcoding → Transcode; crosses_memory → MemoryCopy; else → Direct. Fields mapped from the exact same source expressions (site.crosses_memory, options.needs_transcoding()).
  • Body dispatch — the new match class calls the same generator per class, and the resource_rep_imports 4th argument to generate_memory_copy_adapter survived.
  • Inline guard — the three negations are correct De Morgan duals: has_resource_rep_calls = !resource_rep_calls.is_empty()!has_… = is_empty() (and likewise resource_new, and has_post_return = callee_post_return.is_some()!has_… = is_none()). The && chain is pure conjunction over side-effect-free reads, so short-circuit order cannot change the result.
  • AdapterClass::Async unreachable! — verified genuinely unreachable: the generate loop (fact.rs ~12196) tests is_async_lift first and continues before the generate_adapter call, so Async never flows into the seam.
  • Truth-table equivalence — for all 2⁶ BoundaryFacts combinations, the new (class, inline_eligible) equals the original inline dispatch's output (class is a 4-row table on (crosses_memory, needs_transcoding); inline_eligible is the same conjunction post-De-Morgan). The 8 in-module unit tests exercise every branch of that table.

meld-core/src/adapter/mod.rs — NO FINDINGS

Change is inert: a one-line pub(crate) mod call_lowering; registration. The AdapterClass enum (the only fusion-relevant content) is unchanged by this PR.

Per the Mythos honesty bar, no finding is reported (nothing to report — the extraction is a mechanical boolean identity, verified by inspection and by the exhaustive branch tests). No Kani/PoC pair is attached because there is no bug to reproduce.

@avrabe avrabe added the mythos-pass-done Mythos delta-pass completed on Tier-5 file changes; findings (or NO FINDINGS) attached to PR label Jul 17, 2026
@github-actions

Copy link
Copy Markdown

LS-N verification gate

59/59 approved LS entries verified

count
Passed (≥1 test, all green) 59
Failed (≥1 test failure) 0
Missing (no ls_*_NN_* test found) 0

Approved loss-scenarios.yaml entries are expected to have a
regression test named ls_<letter>_<num>_* (e.g. LS-A-11
ls_a_11_*). The gate runs each prefix via cargo test --lib --no-fail-fast and aggregates pass/fail/missing.

Failed LS entries

(none)

Missing regression tests

(none)

Updated automatically by tools/post_verification_comment.py.
Source of truth: safety/stpa/loss-scenarios.yaml.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Mythos delta-pass (auto)

NO FINDINGS across 3 Tier-5 file(s)

File Verdict Hypothesis
`` ✅ NO FINDINGS
`` ✅ NO FINDINGS
`` ✅ NO FINDINGS

Auto-run via anthropics/claude-code-action@v1
(SHA-pinned) on the touched Tier-5 files, using the
maintainer's Max-plan OAuth token. See
.github/workflows/mythos-auto.yml and
scripts/mythos/discover.md.

@avrabe

avrabe commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Triage of the auto-Mythos finding (crosses_memory=false, needs_transcoding=true)

The auto gate is doing its job — but two things need separating:

1. This is NOT a regression from this PR. The extraction is behavior-preserving. The flagged path (same memory, different encoding → Direct → transcoding skipped) is identical to main's removed inline code: the old dispatch's else → Direct branch also caught crosses_memory=false regardless of needs_transcoding, and inlined it under the same guard. Verified term-by-term + 2⁶ truth-table (see the NO-FINDINGS comment above). So resolve_call_lowering_plan faithfully preserves the decision; it did not introduce the gap.

2. The finding does surface a REAL, pre-existing latent bug in main. Grounded:

  • crosses_memory is set purely from memory strategy (resolver.rs:2866-2879): under --memory shared it is always false, independent of encoding.
  • needs_transcoding = caller_encoding != callee_encoding, set independently (resolver.rs:2972, analyze_call_site fact.rs:5394-5403).
  • The class dispatch only reaches Transcode when site.crosses_memory is true, so a sync, same-memory, mixed-encoding call routes to Direct — which is a thin shim that does not transcode. No upstream validation rejects mixed encodings under shared memory (only the LS-P-17 warning).
  • Net: meld fuse --memory shared of a UTF-8 component calling a UTF-16 component silently delivers untranscoded bytes.

3. meld already hard-fails the analogous ASYNC case. guard_async_cross_encoding_strings (fact.rs:12139, #272) fails loud — "async cross-encoding string transcoding is not yet supported" — rather than emit a silent mis-copy. The sync same-memory path is the unguarded twin of that exact decision, and silent-corruption-vs-loud-failure is the call meld made in #351/#352/#355.

Resolution: the honest fix is to add the sync counterpart of the async guard — a hard-fail on needs_transcoding && !crosses_memory — turning the silent miscompile into a loud, tested error and clearing this finding legitimately (rather than overriding the gate on a known silent-corruption path). Doing that as part of this PR since the seam is precisely where the decision lives.

… seam (#361)

The auto-Mythos delta-pass on the call-lowering seam surfaced a real,
pre-existing latent bug (faithfully preserved by inc 2's behavior-preserving
extraction, not introduced by it): a sync call that needs string transcoding
but does NOT cross a memory boundary (`needs_transcoding && !crosses_memory`
— e.g. `--memory shared` fusion of a UTF-8 component calling a UTF-16 one)
routed to `AdapterClass::Direct`, a thin shim that does not transcode, so the
string bytes were delivered verbatim in the wrong encoding — a silent
miscompile. No upstream validation rejected mixed encodings under shared
memory (only the LS-P-17 warning).

meld already fails loud on the identical ASYNC case
(`guard_async_cross_encoding_strings`, fact.rs:12139, #272). This adds the
sync twin: `resolve_call_lowering_plan` now returns `Result` and hard-fails
with `AdapterGeneration` on that boundary rather than emit a silently
mis-transcoding Direct shim — the #351/#352/#355 silent-corruption-vs-loud-
failure choice, applied consistently.

Actually supporting same-memory transcoding (a Transcode-class adapter without
the cross-memory realloc+copy assumption) is tracked in #361; the seam is the
right home to resolve that boundary to a real adapter later.

- call_lowering.rs: resolve_call_lowering_plan -> Result; guard + 9th unit
  test (same_memory_transcoding_hard_fails); the class + inline truth table is
  otherwise unchanged.
- fact.rs: generate_adapter propagates the seam's Result via `?`.

Full meld-core suite green (0 failures; guard fires on no existing fixture —
the path was silently latent); fmt + clippy clean.

Refs #360, #361, #272, ADR-7 (path-H inc 2)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@avrabe

avrabe commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Mythos update — finding resolved by the #361 hard-fail

The auto-gate finding is addressed at the source: resolve_call_lowering_plan now hard-fails (AdapterGeneration) on needs_transcoding && !crosses_memory instead of returning {Direct, inline_eligible}. So the silent-skip the auto-agent flagged can no longer occur — the seam refuses that boundary rather than mis-lower it.

Re-verified on the new commit (a1d7d0c):

  • Guard is exact — fires iff needs_transcoding && !crosses_memory. Same-encoding calls (needs_transcoding == false) don't fire; legitimate cross-memory transcode (crosses_memory == true) still routes to Transcode. No over- or under-firing.
  • Threaded correctlygenerate_adapter propagates the seam's Result via ?.
  • Fires on no existing fixture — full meld-core suite green (0 failures across all suites); the new same_memory_transcoding_hard_fails unit test covers the guard; the other 8 truth-table tests are unchanged.

The class + inline-eligibility decision is otherwise the same behavior-preserving extraction verified earlier (2⁶ truth-table equivalence).

@avrabe
avrabe merged commit 82021ee into main Jul 18, 2026
16 checks passed
@avrabe
avrabe deleted the refactor/adr7-call-lowering-seam branch July 18, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mythos-pass-done Mythos delta-pass completed on Tier-5 file changes; findings (or NO FINDINGS) attached to PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant