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
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,51 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.50.0] - 2026-08-19

### Added
- **`--profile safety`: refuse to infer safety-relevant properties (SR-68, #386)**
— ADR-7 gave meld a dual identity (the generic RFC-46 reference fuser *and* a
sealed-safety product, both first-class) and specified two attested profiles;
this adds the signal that separates them. Under `--profile safety` every
safety-relevant property must be **stated**, and inferring one is a hard error
(`Error::SafetyProfileViolation`) rather than a warning. First enforced
property (ADR-4, "explicit, not auto"): the **memory strategy** — `--memory
auto` is refused, because it selects the inter-component isolation model, which
decides whether a fault in one component can reach another component's state.
The check runs *before* fusion, so a violation is a build-configuration error
that cannot depend on input shape or leave a partial artifact.

`--profile ecosystem` (the default) is **unchanged** — nothing breaks. The
profile *gates*, it never transforms: a build that succeeds under `safety`
emits byte-identical output to the same explicit invocation under `ecosystem`.
A profile was needed rather than "strict when attested" because `attestation`
defaults on and `--memory` defaults to `auto`, so "attested + auto" is the
ordinary invocation. See `meld docs profiles`.

### Fixed
- **`--memory shared --address-rebase` no longer cries wolf (#386)** — it warned
"UNSOUND" unconditionally, including for `--emit-relocs` inputs where it is
not, and which is the path `--pack-rebase` (SR-57) and `--share-stack` (SR-66)
are built on and that the falcon supplier validated on real components (#370).
The text predated the reloc *consumer* (#326→#340): a reloc-covered input is
rebased at the source point, so a pointer computed from it is correct by
construction. The warning now fires only when an input actually lacks reloc
metadata — where the residual risk is real and was already reported precisely,
per-module, by the address strategy.
- **`MemoryStrategy::Auto` documentation contradicted the implementation (#386)**
— the public rustdoc described Auto resolving to shared memory + rebasing,
which it has never done since #326. Corrected, along with the module-level docs
and a dangling reference to a function that does not exist.

### Changed
- `Auto` now **reports** the single-address-space build that the inputs would
support (naming the explicit flags) instead of silently leaving it undiscovered
— and still never escalates to it. Escalating on "inputs look reloc-covered"
is ruled out permanently: the probe proves reloc *presence*, not *coverage*
(undecidable in general, #339), and a safety tool should not move to the
stricter-contract strategy on its own.

## [0.49.0] - 2026-08-14

### 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.49.0"
version = "0.50.0"
authors = ["PulseEngine <https://github.com/pulseengine>"]
edition = "2024"
license = "Apache-2.0"
Expand Down
44 changes: 44 additions & 0 deletions meld-cli/docs/concept-profiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Build profiles — ecosystem and safety

meld has two identities, and both are first-class: the generic Component-Model
fuser anyone can use, and a tool you can put in a functional-safety toolchain.
`--profile` says which one this build is.

The profiles do **not** fuse differently. A build that succeeds under
`--profile safety` produces byte-identical output to the same explicit invocation
under `--profile ecosystem`. What changes is **how much meld is allowed to decide
for you**.

## `--profile ecosystem` (default)

Convenience defaults apply. Where a property is unset, meld picks a sensible,
sound value and tells you what it picked. `--memory auto` selects a memory
strategy for you; advisory checks warn.

## `--profile safety`

Every safety-relevant property must be **stated**, not inferred. Inferring one is
a hard error instead of a warning.

Enforced today:

- **The memory strategy must be explicit.** `--memory auto` is refused. The
memory strategy selects the inter-component isolation model — one shared
address space, or one memory per component — which decides whether a fault in
one component can reach another component's state. That is not a decision a
build should inherit silently. Pass `--memory multi` (isolation preserved) or
`--memory shared` (single address space; add `--address-rebase`, and build
every input with `--emit-relocs`).

The reasoning is the same one behind meld's other loud failures: for a property
that changes what the artifact *guarantees*, a wrong-but-plausible default is
worse than a stopped build. You can always state the value you want — the profile
only insists that you state it.

## Why a profile rather than "strict when attested"

Attestation is on by default, and so is `--memory auto`. Enforcing on
"attested build" would therefore fail the ordinary `meld fuse a.wasm b.wasm`
invocation, which would break the ecosystem identity to serve the safety one.
The profile is the explicit signal that separates them, so neither identity is
compromised.
5 changes: 5 additions & 0 deletions meld-cli/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ pub const TOPICS: &[Topic] = &[
"Static fusion — what meld does",
"concept-fusion.md"
),
topic!(
"profiles",
"Build profiles — ecosystem and safety",
"concept-profiles.md"
),
topic!(
"memory-strategies",
"Memory strategies — auto, multi, shared",
Expand Down
36 changes: 35 additions & 1 deletion meld-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

use anyhow::{Context, Result, anyhow};
use clap::{Parser, Subcommand};
use meld_core::{DwarfHandling, Fuser, FuserConfig, FusionStats, MemoryStrategy, OutputFormat};
use meld_core::{
DwarfHandling, Fuser, FuserConfig, FusionStats, MemoryStrategy, OutputFormat, Profile,
};
use std::fs;
use std::path::Path;
use std::time::Instant;
Expand Down Expand Up @@ -65,6 +67,16 @@ enum Commands {
#[arg(long, default_value = "auto")]
memory: String,

/// Build profile: 'ecosystem' (default) or 'safety'. Under 'safety'
/// every safety-relevant property must be stated explicitly — inferring
/// one is a hard error rather than a warning (ADR-7's sealed-safety
/// profile; ADR-4 "explicit, not auto"). Today that means `--memory`
/// must be given: 'auto' is refused. A build that passes under 'safety'
/// produces byte-identical output to the same explicit invocation under
/// 'ecosystem' — the profile only decides what may be inferred.
#[arg(long, default_value = "ecosystem")]
profile: String,

/// Rebase memory addresses for shared memory (experimental).
/// Only valid with --memory shared; "auto" decides it itself.
#[arg(long)]
Expand Down Expand Up @@ -241,11 +253,13 @@ fn main() -> Result<()> {
opaque_rep,
pack_rebase,
share_stack,
profile,
}) => {
fuse_command(
inputs,
output,
memory,
profile,
address_rebase,
pack_rebase,
share_stack,
Expand Down Expand Up @@ -325,6 +339,7 @@ fn fuse_command(
inputs: Vec<String>,
output: String,
memory: String,
profile: String,
address_rebase: bool,
pack_rebase: bool,
share_stack: bool,
Expand All @@ -344,6 +359,24 @@ fn fuse_command(
env!("CARGO_PKG_VERSION")
);

// Parse build profile (ADR-7: ecosystem vs sealed-safety).
let profile = match profile.as_str() {
"ecosystem" => Profile::Ecosystem,
"safety" => {
println!(
"Safety profile: every safety-relevant property must be stated \
explicitly (ADR-4: explicit, not auto)"
);
Profile::Safety
}
other => {
return Err(anyhow!(
"Invalid --profile: {}. Use 'ecosystem' or 'safety'",
other
));
}
};

// Parse memory strategy
let memory_strategy = match memory.as_str() {
"auto" => {
Expand Down Expand Up @@ -453,6 +486,7 @@ fn fuse_command(
};

let config = FuserConfig {
profile,
memory_strategy,
attestation: !no_attestation,
reproducible,
Expand Down
1 change: 1 addition & 0 deletions meld-core/benches/fusion_benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fn bench_config() -> FuserConfig {
address_rebasing: false,
pack_rebase: false,
share_stack: false,
profile: meld_core::Profile::Ecosystem,
preserve_names: false,
custom_sections: CustomSectionHandling::Drop,
dwarf_handling: meld_core::DwarfHandling::Strip,
Expand Down
8 changes: 8 additions & 0 deletions meld-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ pub enum Error {
#[error("canonical ABI error: {0}")]
CanonicalAbi(String),

/// A safety-profile requirement was not met (`--profile safety`).
///
/// Under the sealed-safety profile (ADR-7) meld refuses to *infer* a
/// safety-relevant property that the build should have *declared*. The
/// ecosystem profile keeps the same condition as an advisory warning.
#[error("safety profile: {0}")]
SafetyProfileViolation(String),

/// Same core module instantiated more than once in a component
#[error(
"component {component_idx} instantiates core module {module_idx} more than once (multiply-instantiated modules are not yet supported)"
Expand Down
Loading
Loading