Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,55 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.51.0] - 2026-08-19

### Fixed
- **Shipped artifacts recorded almost no build configuration (SR-28 / SR-69)** —
a traceability defect. SR-28 requires every `FuserConfig` field to be recorded
in the fusion attestation so an auditor holding only the artifact can
reconstruct how it was fused. The parameter-recording code, however, sat behind
the optional `attestation` (wsc) Cargo feature, which **release builds do not
enable** — so every published meld artifact recorded nothing beyond the memory
strategy. In particular `--share-stack` (which carries a documented soundness
envelope), `--pack-rebase`, `--address-rebase` and `--profile` were all
**unattested**: an artifact could not be shown to have been built with, or
without, them.

**Artifacts produced by v0.50.0 and earlier therefore carry no configuration
attestation.** If you rely on meld attestations as audit evidence, artifacts
must be re-fused with v0.51.0 or later to record the build configuration.

The build configuration is now recorded on the default (shipped) path as a
typed `parameters` object — deliberately not a map, so its key order is fixed
by declaration and stays byte-stable under `--reproducible`. SR-28 completeness
is additionally enforced **at compile time**: the parameter-building function
destructures `FuserConfig` exhaustively, so adding a config field fails the
build until it is recorded or explicitly acknowledged as not a build parameter.
The previous SR-28 sentinel asserted against a map it built inline in the test,
which is why three fields had already slipped past it; it now asserts against
the real serialized attestation.

### Added
- **Per-boundary strategy is attested and observable (SR-69, ADR-7)** — meld now
records one entry per fused cross-component call: the caller/callee component
and module, the callee function and interface, the call-lowering class chosen
(`direct` / `memory-copy` / `transcode` / `async-lift`) and how the call was
**actually wired** (`inlined-direct` / `widening-wrapper` / `thunk`). This
satisfies ADR-7's requirement that each boundary's strategy be declared,
attested and observable rather than inferable only from aggregate counts.

The wiring field is captured at the wiring step rather than from the
call-lowering seam's inline *eligibility*, because a widening wrapper takes
precedence over inlining — eligibility alone would misreport what shipped.

- **`meld fuse --explain`** — prints those per-boundary decisions, with a tally of
how many boundaries are direct / memory-copy / transcode / async-lift and how
many were wired with nothing interposed. The same records are embedded in the
attestation, so a shipped artifact can be audited after the fact.

Neither addition perturbs artifact hashes: the attestation hash is computed over
the module with the attestation and provenance sections stripped.

## [0.50.0] - 2026-08-19

### Added
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ exclude = [
]

[workspace.package]
version = "0.50.0"
version = "0.51.0"
authors = ["PulseEngine <https://github.com/pulseengine>"]
edition = "2024"
license = "Apache-2.0"
Expand Down
76 changes: 76 additions & 0 deletions meld-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ enum Commands {
#[arg(long)]
stats: bool,

/// Explain the per-boundary strategy: for every fused cross-component
/// call, the call-lowering class chosen for it (direct / memory-copy /
/// transcode / async-lift) and how it was wired (inlined-direct /
/// widening-wrapper / thunk). The same records are embedded in the
/// fusion attestation, so a shipped artifact can be audited after the
/// fact (ADR-7: per-boundary strategy declared, attested, observable).
#[arg(long)]
explain: bool,

/// Disable attestation in output
#[arg(long)]
no_attestation: bool,
Expand Down Expand Up @@ -254,12 +263,14 @@ fn main() -> Result<()> {
pack_rebase,
share_stack,
profile,
explain,
}) => {
fuse_command(
inputs,
output,
memory,
profile,
explain,
address_rebase,
pack_rebase,
share_stack,
Expand Down Expand Up @@ -340,6 +351,7 @@ fn fuse_command(
output: String,
memory: String,
profile: String,
explain: bool,
address_rebase: bool,
pack_rebase: bool,
share_stack: bool,
Expand Down Expand Up @@ -578,6 +590,10 @@ fn fuse_command(
println!("Output: {} ({} bytes)", output, fused_bytes.len());

// Show statistics
if explain {
print_boundaries(&stats);
}

if show_stats {
print_stats(&stats, total_input_size, elapsed);
} else {
Expand All @@ -603,6 +619,66 @@ fn fuse_command(
}

/// Print detailed statistics
/// ADR-7 `--explain`: report the strategy chosen for every fused
/// cross-component call boundary. The same records are embedded in the fusion
/// attestation, so this is the live view of an artifact-auditable fact.
fn print_boundaries(stats: &FusionStats) {
println!();
println!("Boundary strategies");
println!("{}", "=".repeat(50));

if stats.boundaries.is_empty() {
println!();
println!(" (no fused cross-component calls — nothing to report)");
return;
}

println!();
for b in &stats.boundaries {
let memory = if b.crosses_memory {
"cross-memory"
} else {
"same-memory"
};
println!(
" c{}m{} -> c{}m{} {}::{}",
b.from_component, b.from_module, b.to_component, b.to_module, b.interface, b.function
);
println!(
" lowering: {:<12} wiring: {:<16} {}",
b.lowering, b.wiring, memory
);
}

// A short tally so the shape is visible without reading every line.
let mut direct = 0usize;
let mut copy = 0usize;
let mut transcode = 0usize;
let mut asynch = 0usize;
let mut inlined = 0usize;
for b in &stats.boundaries {
match b.lowering.as_str() {
"direct" => direct += 1,
"memory-copy" => copy += 1,
"transcode" => transcode += 1,
_ => asynch += 1,
}
if b.wiring == "inlined-direct" {
inlined += 1;
}
}
println!();
println!(
" {} boundaries: {} direct, {} memory-copy, {} transcode, {} async-lift ({} wired with nothing interposed)",
stats.boundaries.len(),
direct,
copy,
transcode,
asynch,
inlined
);
}

fn print_stats(stats: &FusionStats, total_input_size: usize, elapsed: std::time::Duration) {
println!();
println!("Fusion Statistics");
Expand Down
Loading
Loading