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
+98 -2
View File
@@ -34,8 +34,8 @@ pub struct ClientState {
/// Validates primitive wasm-bindgen values before application code runs.
///
/// Primitive arguments keep JavaScript from owning a second binary codec. The
/// ordinary effect result remains postcard-encoded by `hemx-core`.
/// Primitive arguments keep JavaScript from owning a second event/state codec.
/// The ordinary effect result uses `hemx-core`'s canonical `EffectBatch` codec.
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn decode_client_inputs(
@@ -149,6 +149,102 @@ mod tests {
); // req: client_local/008
}
#[test]
fn client_input_boundary_accepts_limits_and_preserves_values() {
let kind = "k".repeat(256);
let value = "v".repeat(64 * 1024);
let key = "x".repeat(1024);
let state = "s".repeat(1024 * 1024);
let (event, decoded_state) = decode_client_inputs(
1,
kind.clone(),
Some(value.clone()),
Some(true),
Some(key.clone()),
1,
state.clone(),
)
.expect("documented client-local limits are inclusive");
assert_eq!(
event,
ClientEvent {
kind,
value: Some(value),
checked: Some(true),
key: Some(key),
}
);
assert_eq!(decoded_state, ClientState { encoded: state });
// req: client_local/005 test req: client_local/015 test req: client_local/016 test req: client_local/017 test req: client_local/018 test req: client_local/019 test
}
#[test]
fn client_input_boundary_rejects_invalid_versions_and_payload_sizes() {
let decode = |event_version, kind, value, key, state_version, state| {
decode_client_inputs(event_version, kind, value, None, key, state_version, state)
};
for (result, expected) in [
(
decode(0, "click".into(), None, None, 1, String::new()),
"unsupported client-local event ABI version 0; expected 1",
),
(
decode(1, String::new(), None, None, 1, String::new()),
"invalid client-local event payload: event kind must contain 1..=256 bytes",
),
(
decode(1, "k".repeat(257), None, None, 1, String::new()),
"invalid client-local event payload: event kind must contain 1..=256 bytes",
),
(
decode(
1,
"input".into(),
Some("v".repeat(64 * 1024 + 1)),
None,
1,
String::new(),
),
"invalid client-local event payload: value exceeds 65536 bytes",
),
(
decode(
1,
"keydown".into(),
None,
Some("k".repeat(1025)),
1,
String::new(),
),
"invalid client-local event payload: key exceeds 1024 bytes",
),
(
decode(1, "click".into(), None, None, 0, String::new()),
"unsupported client-local state ABI version 0; expected 1",
),
(
decode(
1,
"click".into(),
None,
None,
1,
"s".repeat(1024 * 1024 + 1),
),
"invalid client-local state payload: state exceeds 1048576 bytes",
),
] {
assert_eq!(
result.expect_err("invalid client input must fail closed"),
expected
);
}
// req: client_local/008 test req: client_local/015 test req: client_local/016 test req: client_local/017 test req: client_local/018 test req: client_local/019 test
}
#[test]
fn client_handler_uses_the_ordinary_effect_wire_format() {
let fingerprint = BuildFingerprint(17);