feat(v1): harden typed runtime boundaries

Elect one canonical EffectBatch codec, remove the parallel postcard API, and strengthen fail-closed host, form, sync, WASM, macro, generated-contract, and test-harness proofs with mutation-driven coverage.

req: wire/008

req: wire/009

req: wire/010

req: push/008

req: client_local/015

req: client_local/016

req: client_local/017

req: client_local/018

req: client_local/019

req: sync/024

req: sync/025

req: sync/026

req: sync/027

req: sync/028

req: sync/029

req: test/020

req: test/021
This commit is contained in:
slhx agent
2026-07-16 22:27:28 +02:00
parent e7211df4c5
commit e9ced4e0c1
19 changed files with 1529 additions and 198 deletions
+128
View File
@@ -2325,6 +2325,134 @@ fn parse_error(path: &Path, err: impl std::fmt::Display) -> io::Error {
mod tests {
use super::*;
#[test]
fn generated_contract_fingerprint_and_client_bootstrap_are_deterministic() {
let resource = |symbol: &str, component: &str, id| Resource {
symbol: symbol.into(),
ident: symbol.into(),
component: component.into(),
keyed: false,
id,
};
let mut resources = Resources::default();
resources
.slots
.insert("slot".into(), resource("slot", "page", 11));
resources
.handles
.insert("save".into(), resource("save", "page", 12));
resources
.atoms
.insert("count".into(), resource("count", "page", 13));
resources.forms.insert(
"profile".into(),
FormResource {
resource: resource("profile", "page", 14),
controls: vec![
GeneratedControl {
name: "email".into(),
kind: ControlKind::Text,
required: true,
},
GeneratedControl {
name: "nickname".into(),
kind: ControlKind::Text,
required: false,
},
],
},
);
let expected_parts = vec![
SURFACE_SCHEMA_VERSION,
EFFECT_BATCH_ABI_VERSION,
RUNTIME_ABI_VERSION,
0,
11,
1,
12,
3,
13,
2,
14,
2,
stable_id("form-field", "email"),
1,
stable_id("form-field", "nickname"),
0,
];
assert_eq!(resources.fingerprint_parts(), expected_parts);
let generated = resources.generated_rs(false);
assert!(generated.starts_with("// @generated by hemx-build. Do not edit.\n"));
assert!(generated.contains("pub const BUILD_FINGERPRINT"));
assert!(generated.contains(
"\n#[allow(non_upper_case_globals)]\npub mod page {\n #[derive(Clone, Copy)]\n pub struct Component;\n"
));
assert!(generated.contains("pub const slot: SlotTarget"));
assert!(generated.contains("pub const save: ::hemx::Handle"));
assert!(generated.contains("pub const count: ::hemx::Atom"));
assert!(generated.contains("pub const profile: ::hemx::Form"));
assert!(generated.contains("\n pub mod targets {\n"));
let generated_with_globals = resources.generated_rs(true);
assert!(generated_with_globals.contains("pub mod advanced {"));
assert!(generated_with_globals.contains("pub mod slots {"));
assert_eq!(Resources::default().client_bootstrap().unwrap(), "");
resources
.client_handlers
.extend(["save".into(), "toggle".into()]);
assert_eq!(
resources.client_bootstrap().unwrap_err().to_string(),
"client-local handlers require one data-hemx-client-module on a hemx root"
);
resources.client_modules.insert("/app.wasm".into());
let bootstrap = resources.client_bootstrap().unwrap();
assert!(bootstrap.starts_with(
"import init, { __hemx_client_save, __hemx_client_toggle } from \"/app.wasm\";\nawait init();\n"
));
assert!(
bootstrap.contains("window.hemx.registerClientHandler(\"save\", __hemx_client_save);")
);
assert!(bootstrap.contains("data-hemx-client-ready"));
resources.client_modules.insert("/other.wasm".into());
assert_eq!(
resources.client_bootstrap().unwrap_err().to_string(),
"client-local handlers must share one data-hemx-client-module per generated application"
);
// test req: build/007 req: abi/003 req: client_local/011
}
#[test]
fn app_builder_returns_output_errors_instead_of_panicking() {
let root = std::env::temp_dir().join(format!(
"hemx-build-error-{}-{}",
std::process::id(),
std::thread::current().name().unwrap_or("test")
));
let out = root.join("out");
std::fs::create_dir_all(out.join("hemx.generated.rs")).unwrap();
let result = std::panic::catch_unwind(|| app().out_dir(&out).run());
assert!(result.is_ok(), "ordinary output errors must not panic");
assert!(result.unwrap().is_err());
std::fs::remove_dir_all(out.join("hemx.generated.rs")).unwrap();
std::fs::create_dir_all(out.join("hemx.syms")).unwrap();
let result = std::panic::catch_unwind(|| app().out_dir(&out).run());
assert!(result.is_ok(), "symbol output errors must not panic");
assert!(result.unwrap().is_err());
std::fs::remove_dir_all(out.join("hemx.syms")).unwrap();
std::fs::create_dir_all(out.join("hemx.client.js")).unwrap();
let result = std::panic::catch_unwind(|| app().out_dir(&out).run());
assert!(result.is_ok(), "client output errors must not panic");
assert!(result.unwrap().is_err());
let _ = std::fs::remove_dir_all(root);
// test req: build/004
}
#[test]
fn no_op_build_preserves_generated_artifact_timestamps() {
// req: build/009