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
+209 -7
View File
@@ -1,5 +1,6 @@
use hemx_core::{
Atom, Effect, GeneratedTarget, KeyedSlot, Payload, ResourceId, ResourceKind, ResourceRef, Slot,
Atom, Effect, Form, GeneratedTarget, KeyedSlot, NavigateMode, Payload, ResourceId,
ResourceKind, ResourceRef, ScopeKey, Slot,
};
#[test]
@@ -84,6 +85,201 @@ fn finds_keyed_slot_targets() {
assert_eq!(inspected.ops().len(), 1);
}
#[test]
fn inspector_predicates_bind_operation_target_scope_kind_and_payload() {
// test req: test/008 req: test/009 req: test/018
let expected = TestTarget(ResourceKind::Slot, 42);
let other = TestTarget(ResourceKind::Slot, 7);
let resource = ResourceId::new(ResourceKind::Slot, 42);
let keyed_ref = ResourceRef::scoped(resource, ScopeKey::KeyValue("row-1".into()));
let form = Form::<()>::new(11);
let expected_emit = Effect::Emit {
name: "saved".into(),
payload: "card 42 saved".into(),
};
let inspected = hemx_test::inspect(vec![
Effect::Put {
target: ResourceRef::unscoped(other.__hemx_resource_id()),
payload: Payload::Text("decoy needle".into()),
},
Effect::Put {
target: ResourceRef::unscoped(resource),
payload: Payload::Text("expected text".into()),
},
Effect::Put {
target: keyed_ref.clone(),
payload: Payload::Html("<li data-key=\"row-1\">replacement</li>".into()),
},
Effect::Insert {
target: ResourceRef::unscoped(resource),
key: "row-2".into(),
payload: Payload::Html("<li>inserted</li>".into()),
},
Effect::Prepend {
target: ResourceRef::unscoped(resource),
key: "row-0".into(),
payload: Payload::Html("<li>prepended</li>".into()),
},
Effect::Remove {
target: ResourceRef::unscoped(resource),
key: Some("row-old".into()),
},
Effect::Focus {
target: ResourceRef::unscoped(form.id()),
},
Effect::Navigate {
url: "/cards/42".into(),
mode: NavigateMode::Push,
scroll: hemx_core::ScrollBehavior::Preserve,
title: None,
},
expected_emit.clone(),
Effect::Emit {
name: "hemx:form-reset".into(),
payload: form.id().id.to_string(),
},
]);
assert!(!inspected.is_empty());
assert_eq!(inspected.op_count(), 10);
assert!(inspected.contains(&expected_emit));
assert!(!inspected.contains(&Effect::Emit {
name: "saved".into(),
payload: "wrong".into(),
}));
assert!(inspected.has_resource(resource));
assert!(inspected.has_resource(other.__hemx_resource_id()));
assert!(inspected.has_target(expected));
assert!(inspected.has_target(other));
assert!(!inspected.has_target(TestTarget(ResourceKind::Slot, 99)));
assert!(inspected.updates_text(expected));
assert!(inspected.updates_text_containing(expected, "expected"));
assert!(!inspected.updates_text_containing(expected, "decoy"));
assert!(inspected.updates_text(other));
assert!(!inspected.updates_text(TestTarget(ResourceKind::Slot, 99)));
assert!(inspected.updates_html(expected));
assert!(inspected.updates_html_containing(expected, "replacement"));
assert!(!inspected.updates_html_containing(expected, "missing"));
assert!(!inspected.updates_html(TestTarget(ResourceKind::Slot, 99)));
assert!(inspected.replaces_keyed_html_containing(expected, "row-1", "replacement"));
assert!(!inspected.replaces_keyed_html_containing(expected, "row-2", "replacement"));
assert!(inspected.inserts_html_containing(expected, "row-2", "inserted"));
assert!(!inspected.inserts_html_containing(expected, "row-0", "inserted"));
assert!(inspected.removes_key(expected, "row-old"));
assert!(!inspected.removes_key(expected, "row-2"));
assert!(inspected.pushes_to("/cards/42"));
assert!(!inspected.pushes_to("/cards/7"));
assert!(inspected.payload_contains("prepended"));
assert!(!inspected.payload_contains("absent"));
assert!(inspected.payload_excludes("absent"));
assert!(!inspected.payload_excludes("expected"));
assert!(inspected.payload_excludes_key("absent"));
assert!(!inspected.payload_excludes_key("row-1"));
assert_eq!(
inspected.target_html_containing(expected, "replacement"),
Some("<li data-key=\"row-1\">replacement</li>")
);
assert_eq!(
inspected.target_html_containing(expected, "inserted"),
Some("<li>inserted</li>")
);
assert_eq!(
inspected.target_html_containing(expected, "prepended"),
Some("<li>prepended</li>")
);
assert_eq!(inspected.target_html_containing(expected, "missing"), None);
assert!(inspected.emits("saved", "card 42 saved"));
assert!(!inspected.emits("saved", "wrong"));
assert!(inspected.emits_containing("saved", "42"));
assert!(!inspected.emits_containing("other", "42"));
assert!(inspected.has_ref(&keyed_ref));
assert!(inspected.has_ref(&ResourceRef::unscoped(resource)));
assert!(!inspected.has_ref(&ResourceRef::scoped(
resource,
ScopeKey::Field("row-1".into()),
)));
assert!(inspected.has_slot(Slot::<()>::new(42)));
assert!(!inspected.has_slot(Slot::<()>::new(99)));
assert!(inspected.has_keyed_slot(KeyedSlot::<String, ()>::new(42)));
assert!(!inspected.has_keyed_slot(KeyedSlot::<String, ()>::new(99)));
assert!(!inspected.has_atom(Atom::<()>::new(42)));
assert!(inspected.has_form(form));
assert!(!inspected.has_form(Form::<()>::new(12)));
assert!(inspected.resets_form(form));
assert!(!inspected.resets_form(Form::<()>::new(12)));
let empty = hemx_test::inspect(Vec::<Effect>::new());
assert!(empty.is_empty());
assert_eq!(empty.op_count(), 0);
}
#[test]
fn selector_helpers_validate_parts_and_cover_unscoped_variants() {
// test req: test/017
assert_eq!(hemx_test::heading_selector("", 2), "h2");
assert_eq!(hemx_test::list_item_selector(""), "li");
assert_eq!(hemx_test::prose_selector(""), "p");
assert_eq!(
hemx_test::target_selector(TestTarget(ResourceKind::Slot, 1)),
"[data-sid=\"1\"]"
);
assert_eq!(
hemx_test::target_selector(TestTarget(ResourceKind::Atom, 2)),
"[data-aid=\"2\"]"
);
assert_eq!(
hemx_test::target_selector(TestTarget(ResourceKind::Handle, 3)),
"[data-hid=\"3\"]"
);
assert_eq!(
hemx_test::target_selector(TestTarget(ResourceKind::Form, 4)),
"[data-fid=\"4\"]"
);
assert_eq!(
hemx_test::nav_link_selector("/path\\\"quoted"),
"a[href=\"/path\\\\\\\"quoted\"]"
);
hemx_test::assert_rendered_target(
TestTarget(ResourceKind::Slot, 1),
"slot",
"<p data-sid=\"1\"></p>",
);
hemx_test::assert_rendered_handle(
hemx_core::Handle::<()>::new(3),
"handle",
"<button data-hid=\"3\"></button>",
);
assert!(std::panic::catch_unwind(|| {
hemx_test::assert_rendered_target(TestTarget(ResourceKind::Slot, 1), "slot", "<p></p>");
})
.is_err());
assert!(std::panic::catch_unwind(|| {
hemx_test::assert_rendered_handle(
hemx_core::Handle::<()>::new(3),
"handle",
"<button data-other=\"3\"></button>",
);
})
.is_err());
for invalid in ["", "two parts", ".class", "#id", "a>b", "a[b]"] {
assert!(std::panic::catch_unwind(|| hemx_test::class_selector(invalid)).is_err());
}
for call in [
std::panic::catch_unwind(|| hemx_test::element_class_selector("bad tag", "ok")),
std::panic::catch_unwind(|| hemx_test::element_class_selector("span", "bad class")),
std::panic::catch_unwind(|| hemx_test::class_child_selector("bad parent", "li", "row")),
std::panic::catch_unwind(|| hemx_test::class_child_selector("list", "bad tag", "row")),
std::panic::catch_unwind(|| hemx_test::class_child_selector("list", "li", "bad class")),
std::panic::catch_unwind(|| hemx_test::class_descendant_selector("bad parent", "i")),
std::panic::catch_unwind(|| hemx_test::class_descendant_selector("note", "bad tag")),
] {
assert!(call.is_err());
}
}
#[test]
fn builds_generated_handle_form_bodies() {
let handle = hemx_core::Handle::<()>::new(7);
@@ -91,6 +287,10 @@ fn builds_generated_handle_form_bodies() {
let body = hemx_test::handle_form_body(handle, &[("title", "hello world"), ("tag", "a&b")]);
assert_eq!(body, "__h=7&title=hello+world&tag=a%26b");
assert_eq!(
hemx_test::handle_form_body(handle, &[("AZaz09-_.~", "AZaz09-_.~ /é")],),
"__h=7&AZaz09-_.~=AZaz09-_.~+%2F%C3%A9"
);
assert_eq!(hemx_test::unknown_handle_form_body(99), "__h=99");
}
@@ -189,13 +389,15 @@ fn builds_authoring_boundary_selectors() {
assert_eq!(hemx_test::keyed_items_selector("li"), "li[data-key]");
let probe = hemx_test::island_probe_script(
"probe-island",
"orbit",
"1|1|1|probe waiting",
"7|2|8|probe live",
"probe-\"island",
"orbit\\bridge",
"line1\nline2\rline3\tend",
"detail \"quoted\"",
);
assert!(probe.contains("probe-island"));
assert!(probe.contains("probe live"));
assert!(probe.contains(r#"island.id = "probe-\"island";"#));
assert!(probe.contains(r#"const islandName = "orbit\\bridge";"#));
assert!(probe.contains(r#""line1\nline2\rline3\tend""#));
assert!(probe.contains(r#"detail: "detail \"quoted\"""#));
}
#[derive(Clone, Copy)]
+86 -1
View File
@@ -1,6 +1,7 @@
use hemx_test::TestProcess;
use std::net::{TcpListener, TcpStream};
use std::process::Command;
use std::time::Duration;
use std::time::{Duration, Instant};
#[test]
fn process_harness_reports_early_exit_with_context() {
@@ -27,5 +28,89 @@ fn process_harness_reports_early_exit_with_context() {
assert!(message.contains("exited with"), "{message}");
}
#[test]
fn process_harness_waits_for_readiness_and_reaps_on_drop() {
// test req: test/019
let reservation = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = reservation.local_addr().unwrap().to_string();
drop(reservation);
let mut command = Command::new(std::env::current_exe().unwrap());
command
.arg("--exact")
.arg("helper_process_listens")
.arg("--nocapture")
.env("HEMX_TEST_PROCESS_ADDR", &addr);
let process = TestProcess::start(command, "listening helper", &addr, Duration::from_secs(2))
.expect("readiness must observe the helper listener");
assert!(TcpStream::connect(&addr).is_ok());
drop(process);
let deadline = Instant::now() + Duration::from_secs(2);
while TcpStream::connect(&addr).is_ok() && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(10));
}
assert!(
TcpStream::connect(&addr).is_err(),
"drop must reap the helper"
);
}
#[test]
fn process_harness_reports_spawn_and_readiness_timeout_errors() {
// test req: test/019
let spawn_error = match TestProcess::start(
Command::new("/definitely/not/a/hemx/executable"),
"missing helper",
"127.0.0.1:9",
Duration::from_millis(10),
) {
Ok(_) => panic!("spawn failure must be returned, not panic"),
Err(error) => error,
};
assert!(spawn_error
.to_string()
.contains("failed to spawn missing helper"));
let reservation = TcpListener::bind("127.0.0.1:0").unwrap();
let unused_addr = reservation.local_addr().unwrap().to_string();
drop(reservation);
let mut command = Command::new(std::env::current_exe().unwrap());
command
.arg("--exact")
.arg("helper_process_sleeps")
.arg("--nocapture")
.env("HEMX_TEST_PROCESS_SLEEP", "1");
let timeout = match TestProcess::start(
command,
"non-listening helper",
&unused_addr,
Duration::from_millis(100),
) {
Ok(_) => panic!("non-listening process must time out"),
Err(error) => error,
};
let message = timeout.to_string();
assert!(message.contains("timed out"), "{message}");
assert!(message.contains("non-listening helper"), "{message}");
}
#[test]
fn helper_process_exits_successfully() {}
#[test]
fn helper_process_listens() {
let Ok(addr) = std::env::var("HEMX_TEST_PROCESS_ADDR") else {
return;
};
let _listener = TcpListener::bind(addr).expect("bind helper listener");
std::thread::sleep(Duration::from_secs(10));
}
#[test]
fn helper_process_sleeps() {
if std::env::var_os("HEMX_TEST_PROCESS_SLEEP").is_some() {
std::thread::sleep(Duration::from_secs(10));
}
}