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
+244 -4
View File
@@ -411,7 +411,6 @@ impl SyncEffect {
}
pub fn send_patch(patch: FlatPatch) -> Self {
patch.validate().expect("FlatPatch must remain valid");
Self(vec![Effect::Emit {
name: PATCH_EVENT.to_owned(),
payload: patch.payload(),
@@ -426,7 +425,6 @@ impl SyncEffect {
projection: impl IntoEffect,
fingerprint: BuildFingerprint,
) -> Self {
patch.validate().expect("FlatPatch must remain valid");
let projection = projection.into_batch(fingerprint);
let projection_wire = projection
.to_wire()
@@ -469,9 +467,30 @@ mod tests {
.into_batch(hemx_core::BuildFingerprint(7));
assert_eq!(batch.ops.len(), 2);
assert!(matches!(&batch.ops[0], Effect::Emit { name, .. } if name == "projected"));
assert!(
matches!(&batch.ops[1], Effect::Emit { name, payload } if name == PATCH_EVENT && payload.contains("\"projection\":[") && payload.contains("$hemx-interaction"))
let Effect::Emit { name, payload } = &batch.ops[1] else {
panic!("durable sync must end with its patch event");
};
assert_eq!(name, PATCH_EVENT);
let payload: serde_json::Value = serde_json::from_str(payload).unwrap();
let expected_projection = EffectBatch {
abi_version: hemx_core::EFFECT_BATCH_ABI_VERSION,
fingerprint: BuildFingerprint(7),
ops: vec![Effect::Emit {
name: "projected".into(),
payload: "card:1".into(),
}],
}
.to_wire();
assert_eq!(
payload["projection"],
serde_json::Value::Array(
expected_projection
.into_iter()
.map(serde_json::Value::from)
.collect()
)
);
assert_eq!(payload["patch"]["idempotencyKey"], INTERACTION_ID);
}
#[test]
@@ -549,6 +568,227 @@ mod tests {
);
}
#[test]
fn channel_boundary_and_errors_are_explicit() {
let valid = format!("a{}", "x".repeat(127));
assert_eq!(Channel::new(&valid).unwrap().as_str(), valid);
for (value, expected, message) in [
(
"".to_owned(),
ChannelError::Empty,
"sync channel must not be empty",
),
(
format!("a{}", "x".repeat(128)),
ChannelError::TooLong,
"sync channel is too long",
),
(
"board/alpha".to_owned(),
ChannelError::InvalidCharacter,
"sync channel contains an invalid character",
),
] {
let error = Channel::new(value).expect_err("invalid channel must fail closed");
assert_eq!(error, expected);
assert_eq!(error.to_string(), message);
}
// req: sync/024 test
}
#[test]
fn presence_leave_and_projection_preserve_observable_state() {
let channel = Channel::new("board").unwrap();
let mut tracker = PresenceTracker::default();
assert_eq!(
tracker.leave(&channel, &"missing"),
PresenceChange {
changed: false,
count: 0,
}
);
tracker.join(channel.clone(), "ada");
tracker.join(channel.clone(), "grace");
assert_eq!(tracker.count(&channel), 2);
assert_eq!(
tracker.leave(&channel, &"ada"),
PresenceChange {
changed: true,
count: 1,
}
);
assert_eq!(tracker.count(&channel), 1);
assert_eq!(tracker.leave(&channel, &"grace").count, 0);
assert_eq!(tracker.count(&channel), 0);
assert!(!tracker.members.contains_key(&channel));
let effect = Effect::Emit {
name: "presence".into(),
payload: "joined".into(),
};
let projection = PresenceProjection::new(channel, effect.clone());
assert_eq!(projection.into_batch(BuildFingerprint(1)).ops, vec![effect]);
// test req: sync/003 req: sync/005
}
#[test]
fn flat_patch_enforces_identifier_key_and_value_boundaries() {
let valid_identifier = format!("a{}", "x".repeat(127));
let valid_key = format!("a{}", "x".repeat(63));
let patch = FlatPatch::new(
&valid_identifier,
&valid_identifier,
&valid_key,
PatchValue::String("x".repeat(4096)),
)
.expect("documented patch limits are inclusive");
assert_eq!(patch.validate(), Ok(()));
assert_eq!(
FlatPatch::new("", "operation", "field", PatchValue::Boolean(true)),
Err(PatchError::Empty("idempotency_key"))
);
assert_eq!(
FlatPatch::new("actor", "", "field", PatchValue::Boolean(true)),
Err(PatchError::Empty("operation_id"))
);
assert_eq!(
FlatPatch::new(
"actor",
"operation",
format!("a{}", "x".repeat(64)),
PatchValue::Boolean(true),
),
Err(PatchError::TooLong("key"))
);
let cases = [
FlatPatch::new(INTERACTION_ID, "", "field", PatchValue::Boolean(true)),
FlatPatch::new("", INTERACTION_ID, "field", PatchValue::Boolean(true)),
FlatPatch::new("actor", "", "field", PatchValue::Boolean(true)),
FlatPatch::new(
format!("a{}", "x".repeat(128)),
"operation",
"field",
PatchValue::Boolean(true),
),
FlatPatch::new("actor", "bad operation", "field", PatchValue::Boolean(true)),
FlatPatch::new(
"actor",
format!("a{}", "x".repeat(128)),
"field",
PatchValue::Boolean(true),
),
FlatPatch::new("actor", "operation", "", PatchValue::Boolean(true)),
FlatPatch::new(
"actor",
"operation",
format!("a{}", "x".repeat(64)),
PatchValue::Boolean(true),
),
FlatPatch::new("actor", "operation", "1field", PatchValue::Boolean(true)),
FlatPatch::new("actor", "operation", "field.dot", PatchValue::Boolean(true)),
FlatPatch::new(
"actor",
"operation",
"field",
PatchValue::String("x".repeat(4097)),
),
FlatPatch::new(
"actor",
"operation",
"field",
PatchValue::Integer(9_007_199_254_740_992),
),
FlatPatch::new(
"actor",
"operation",
"field",
PatchValue::Integer(-9_007_199_254_740_992),
),
];
for result in cases {
assert!(result.is_err(), "invalid patch boundary must fail closed");
}
assert!(FlatPatch::new(
"actor",
"operation",
"field",
PatchValue::Integer(-9_007_199_254_740_991),
)
.is_ok());
assert_eq!(
FlatPatch::for_interaction("", PatchValue::Boolean(true)),
Err(PatchError::Empty("key"))
);
assert_eq!(
FlatPatch::for_interaction("field", PatchValue::String("x".repeat(4097)),),
Err(PatchError::ValueTooLong)
);
assert_eq!(PatchError::ReservedKey.to_string(), "patch key is reserved");
assert_eq!(
PatchError::ValueTooLong.to_string(),
"patch string value is too long"
);
// req: sync/017 test req: sync/025 test req: sync/026 test req: sync/027 test
}
#[test]
fn flat_patch_json_round_trips_escaped_values_and_rejects_invalid_input() {
let escaped = "quote:\" slash:\\ newline:\n return:\r tab:\t control:\u{1f}";
let patch = FlatPatch::new(
"actor:1",
"operation-1",
"field_name",
PatchValue::String(escaped.into()),
)
.unwrap();
let payload = patch.payload();
let decoded: serde_json::Value = serde_json::from_str(&payload).unwrap();
assert_eq!(decoded["value"], escaped);
assert_eq!(serde_json::from_str::<FlatPatch>(&payload).unwrap(), patch);
for (json, expected) in [
(
r#"{"schemaVersion":2,"idempotencyKey":"actor","operationId":"op","key":"field","value":true}"#,
"unsupported patch schema version 2",
),
(
r#"{"schemaVersion":1,"idempotencyKey":"actor","operationId":"op","key":"field","value":true,"extra":1}"#,
"unknown field `extra`",
),
(
r#"{"schemaVersion":1,"idempotencyKey":"actor","operationId":"op","key":"1field","value":true}"#,
"key contains an invalid character",
),
] {
assert!(
serde_json::from_str::<FlatPatch>(json)
.unwrap_err()
.to_string()
.contains(expected),
"invalid JSON must report {expected}"
);
}
// req: sync/014 test req: sync/028 test req: sync/029 test
}
#[test]
fn send_patch_emits_the_canonical_payload() {
let patch = FlatPatch::for_interaction("done", PatchValue::Boolean(true)).unwrap();
assert_eq!(patch.validate(), Ok(()));
let expected = patch.payload();
assert_eq!(
SyncEffect::send_patch(patch)
.into_batch(BuildFingerprint(4))
.ops,
vec![Effect::Emit {
name: PATCH_EVENT.into(),
payload: expected,
}]
);
// test req: sync/002 req: sync/009
}
#[test]
fn schema_is_flat_and_rejects_reserved_keys() {
let patch = FlatPatch::new(