commit f793ab1c7654f58a10a8f05e67fba58ccc4e8762
parent 31311d6b52a7bb7cee12708662ae170c19db0a39
Author: triesap <tyson@radroots.org>
Date: Tue, 14 Jul 2026 18:56:47 +0000
contract: add public malformed corpus
Diffstat:
12 files changed, 578 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -4384,6 +4384,7 @@ version = "0.1.0-alpha.2"
dependencies = [
"radroots_transport",
"serde",
+ "serde_json",
]
[[package]]
diff --git a/contracts/conformance/vectors/event/nip01_wire.v1.json b/contracts/conformance/vectors/event/nip01_wire.v1.json
@@ -36,6 +36,46 @@
"expected": {
"error": "event_id_mismatch"
}
+ },
+ {
+ "id": "nip01_wire_root_not_object_004",
+ "kind": "event.nip01_wire.invalid",
+ "input": {
+ "raw_json": "[]"
+ },
+ "expected": {
+ "error": "root_not_object"
+ }
+ },
+ {
+ "id": "nip01_wire_missing_content_005",
+ "kind": "event.nip01_wire.invalid",
+ "input": {
+ "raw_json": "{\"id\":\"2a15e33622a155ae231b28bebe390869e67a0e228f77ecfcd652b1ce180a9dde\",\"pubkey\":\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"created_at\":1700000001,\"kind\":0,\"tags\":[],\"sig\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\"}"
+ },
+ "expected": {
+ "error": "missing_content"
+ }
+ },
+ {
+ "id": "nip01_wire_empty_tag_006",
+ "kind": "event.nip01_wire.invalid",
+ "input": {
+ "raw_json": "{\"id\":\"2a15e33622a155ae231b28bebe390869e67a0e228f77ecfcd652b1ce180a9dde\",\"pubkey\":\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"created_at\":1700000001,\"kind\":0,\"tags\":[[]],\"content\":\"{}\",\"sig\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\"}"
+ },
+ "expected": {
+ "error": "empty_tag"
+ }
+ },
+ {
+ "id": "nip01_wire_noncanonical_pubkey_007",
+ "kind": "event.nip01_wire.invalid",
+ "input": {
+ "raw_json": "{\"id\":\"2a15e33622a155ae231b28bebe390869e67a0e228f77ecfcd652b1ce180a9dde\",\"pubkey\":\"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\",\"created_at\":1700000001,\"kind\":0,\"tags\":[],\"content\":\"{}\",\"sig\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\"}"
+ },
+ "expected": {
+ "error": "noncanonical_pubkey"
+ }
}
]
}
diff --git a/contracts/conformance/vectors/mesh/frame_cbor.v1.json b/contracts/conformance/vectors/mesh/frame_cbor.v1.json
@@ -0,0 +1,47 @@
+{
+ "suite": "mesh",
+ "contract_version": "0.1.0",
+ "vectors": [
+ {
+ "id": "mesh_frame_cbor_default_001",
+ "kind": "mesh.frame_cbor.valid",
+ "input": {
+ "hex": "a70001010002656c6f63616c03696d6573736167652d3104182a0519ea6006a0"
+ },
+ "expected": {
+ "message_id": "message-1",
+ "scope": "local"
+ }
+ },
+ {
+ "id": "mesh_frame_cbor_unsupported_version_002",
+ "kind": "mesh.frame_cbor.invalid",
+ "input": {
+ "hex": "a70002010002656c6f63616c03696d6573736167652d3104182a0519ea6006a0"
+ },
+ "expected": {
+ "error": "unsupported_version"
+ }
+ },
+ {
+ "id": "mesh_frame_cbor_wrong_key_order_003",
+ "kind": "mesh.frame_cbor.invalid",
+ "input": {
+ "hex": "a70001020002656c6f63616c03696d6573736167652d3104182a0519ea6006a0"
+ },
+ "expected": {
+ "error": "invalid_cbor"
+ }
+ },
+ {
+ "id": "mesh_frame_cbor_payload_bytes_004",
+ "kind": "mesh.frame_cbor.invalid",
+ "input": {
+ "hex": "a70001010002656c6f63616c03696d6573736167652d3104182a0519ea600643010203"
+ },
+ "expected": {
+ "error": "payload_transmission_forbidden"
+ }
+ }
+ ]
+}
diff --git a/contracts/conformance/vectors/replica_schema/json_models.v1.json b/contracts/conformance/vectors/replica_schema/json_models.v1.json
@@ -0,0 +1,115 @@
+{
+ "suite": "replica_schema",
+ "contract_version": "0.1.0",
+ "vectors": [
+ {
+ "id": "replica_schema_farm_json_valid_001",
+ "kind": "replica_schema.farm_json.valid",
+ "input": {
+ "json": {
+ "id": "farm-alpha",
+ "created_at": "2026-07-14T00:00:00Z",
+ "updated_at": "2026-07-14T00:00:00Z",
+ "d_tag": "farm-alpha",
+ "pubkey": "1111111111111111111111111111111111111111111111111111111111111111",
+ "name": "Radroots Fixture Farm",
+ "about": null,
+ "website": null,
+ "picture": null,
+ "banner": null,
+ "location_primary": null,
+ "location_city": "Fixture City",
+ "location_region": "Fixture Region",
+ "location_country": "Fixture Country"
+ }
+ },
+ "expected": {
+ "id": "farm-alpha"
+ }
+ },
+ {
+ "id": "replica_schema_farm_json_missing_required_002",
+ "kind": "replica_schema.farm_json.invalid",
+ "input": {
+ "json": {
+ "id": "farm-alpha",
+ "created_at": "2026-07-14T00:00:00Z",
+ "updated_at": "2026-07-14T00:00:00Z",
+ "d_tag": "farm-alpha",
+ "pubkey": "1111111111111111111111111111111111111111111111111111111111111111"
+ }
+ },
+ "expected": {
+ "error": "missing_required_field"
+ }
+ },
+ {
+ "id": "replica_schema_trade_product_json_valid_003",
+ "kind": "replica_schema.trade_product_json.valid",
+ "input": {
+ "json": {
+ "id": "trade-product-alpha",
+ "created_at": "2026-07-14T00:00:00Z",
+ "updated_at": "2026-07-14T00:00:00Z",
+ "key": "fixture-carrot",
+ "category": "produce",
+ "title": "Fixture Carrots",
+ "summary": "Synthetic fixture product",
+ "process": "fresh",
+ "lot": "lot-alpha",
+ "profile": "farm-alpha",
+ "year": 2026,
+ "qty_amt": 12.5,
+ "qty_amt_exact": "12.5",
+ "qty_unit": "kg",
+ "qty_label": null,
+ "qty_avail": 12,
+ "price_amt": 6.25,
+ "price_amt_exact": "6.25",
+ "price_currency": "USD",
+ "price_qty_amt": 1.0,
+ "price_qty_amt_exact": "1.0",
+ "price_qty_unit": "kg",
+ "listing_addr": null,
+ "primary_bin_id": null,
+ "verified_primary_bin_id": null,
+ "notes": null
+ }
+ },
+ "expected": {
+ "id": "trade-product-alpha"
+ }
+ },
+ {
+ "id": "replica_schema_trade_product_json_wrong_type_004",
+ "kind": "replica_schema.trade_product_json.invalid",
+ "input": {
+ "json": {
+ "id": "trade-product-alpha",
+ "created_at": "2026-07-14T00:00:00Z",
+ "updated_at": "2026-07-14T00:00:00Z",
+ "key": "fixture-carrot",
+ "category": "produce",
+ "title": "Fixture Carrots",
+ "summary": "Synthetic fixture product",
+ "process": "fresh",
+ "lot": "lot-alpha",
+ "profile": "farm-alpha",
+ "year": "2026",
+ "qty_amt": 12.5,
+ "qty_amt_exact": "12.5",
+ "qty_unit": "kg",
+ "price_amt": 6.25,
+ "price_amt_exact": "6.25",
+ "price_currency": "USD",
+ "price_qty_amt": 1.0,
+ "price_qty_amt_exact": "1.0",
+ "price_qty_unit": "kg"
+ }
+ },
+ "expected": {
+ "error": "wrong_type"
+ }
+ }
+ ]
+}
diff --git a/contracts/conformance/vectors/transport/target_uri.v1.json b/contracts/conformance/vectors/transport/target_uri.v1.json
@@ -0,0 +1,76 @@
+{
+ "suite": "transport",
+ "contract_version": "0.1.0",
+ "vectors": [
+ {
+ "id": "transport_target_uri_generic_canonical_001",
+ "kind": "transport.target_uri.valid",
+ "input": {
+ "uri": "MESH://Node.Example/path?q=1#frag"
+ },
+ "expected": {
+ "canonical_uri": "mesh://node.example/path?q=1#frag"
+ }
+ },
+ {
+ "id": "transport_target_uri_empty_002",
+ "kind": "transport.target_uri.invalid",
+ "input": {
+ "uri": " "
+ },
+ "expected": {
+ "error": "empty_target_uri"
+ }
+ },
+ {
+ "id": "transport_target_uri_whitespace_003",
+ "kind": "transport.target_uri.invalid",
+ "input": {
+ "uri": "bad target"
+ },
+ "expected": {
+ "error": "invalid_target_uri"
+ }
+ },
+ {
+ "id": "transport_target_uri_bad_scheme_004",
+ "kind": "transport.target_uri.invalid",
+ "input": {
+ "uri": "1bad:target"
+ },
+ "expected": {
+ "error": "invalid_target_uri"
+ }
+ },
+ {
+ "id": "transport_nostr_relay_canonical_005",
+ "kind": "transport.nostr_relay_target.valid",
+ "input": {
+ "uri": " WSS://Relay.Example/ "
+ },
+ "expected": {
+ "canonical_uri": "wss://relay.example"
+ }
+ },
+ {
+ "id": "transport_nostr_relay_query_006",
+ "kind": "transport.nostr_relay_target.invalid",
+ "input": {
+ "uri": "wss://relay.example?subscription=1"
+ },
+ "expected": {
+ "error": "invalid_target_uri"
+ }
+ },
+ {
+ "id": "transport_nostr_relay_nonlocal_ws_007",
+ "kind": "transport.nostr_relay_target.invalid",
+ "input": {
+ "uri": "ws://relay.example"
+ },
+ "expected": {
+ "error": "invalid_target_uri"
+ }
+ }
+ ]
+}
diff --git a/crates/mesh/Cargo.toml b/crates/mesh/Cargo.toml
@@ -19,5 +19,8 @@ serde = ["dep:serde"]
radroots_transport = { workspace = true, default-features = false }
serde = { workspace = true, optional = true }
+[dev-dependencies]
+serde_json = { workspace = true, features = ["std"] }
+
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
diff --git a/crates/mesh/tests/mesh.rs b/crates/mesh/tests/mesh.rs
@@ -5,6 +5,7 @@ use radroots_mesh::{
RadrootsMeshPayloadPolicy, RadrootsMeshPolicyDenyReason, RadrootsMeshPrivacyClass,
RadrootsMeshScope, decode_mesh_frame_cbor, encode_mesh_frame_cbor,
};
+use serde_json::Value;
fn default_frame() -> RadrootsMeshFrame {
RadrootsMeshFrame::new(
@@ -438,3 +439,68 @@ fn decoder_rejects_malformed_cbor_shapes() {
RadrootsMeshError::InvalidCbor
);
}
+
+#[test]
+fn checked_in_mesh_cbor_vectors_match_decoder_behavior() {
+ let vectors = include_str!("../../../contracts/conformance/vectors/mesh/frame_cbor.v1.json");
+ let document: Value = serde_json::from_str(vectors).expect("mesh cbor vector json");
+ let entries = document
+ .get("vectors")
+ .and_then(Value::as_array)
+ .expect("mesh cbor vectors");
+
+ for entry in entries {
+ let kind = entry.get("kind").and_then(Value::as_str).expect("kind");
+ let hex = entry
+ .get("input")
+ .and_then(|input| input.get("hex"))
+ .and_then(Value::as_str)
+ .expect("input hex");
+ let bytes = decode_hex(hex);
+ match kind {
+ "mesh.frame_cbor.valid" => {
+ let frame = decode_mesh_frame_cbor(bytes.as_slice()).expect("mesh frame");
+ let expected = entry.get("expected").expect("expected");
+ assert_eq!(
+ frame.message_id,
+ expected
+ .get("message_id")
+ .and_then(Value::as_str)
+ .expect("message id")
+ );
+ assert_eq!(
+ frame.scope_id.label(),
+ expected
+ .get("scope")
+ .and_then(Value::as_str)
+ .expect("scope")
+ );
+ }
+ "mesh.frame_cbor.invalid" => {
+ assert!(decode_mesh_frame_cbor(bytes.as_slice()).is_err());
+ }
+ other => panic!("unknown mesh cbor vector kind {other}"),
+ }
+ }
+}
+
+fn decode_hex(value: &str) -> Vec<u8> {
+ assert_eq!(value.len() % 2, 0, "hex fixture length must be even");
+ value
+ .as_bytes()
+ .chunks_exact(2)
+ .map(|chunk| {
+ let high = hex_nibble(chunk[0]);
+ let low = hex_nibble(chunk[1]);
+ (high << 4) | low
+ })
+ .collect()
+}
+
+fn hex_nibble(byte: u8) -> u8 {
+ match byte {
+ b'0'..=b'9' => byte - b'0',
+ b'a'..=b'f' => byte - b'a' + 10,
+ _ => panic!("hex fixture contains non-lowercase-hex byte"),
+ }
+}
diff --git a/crates/replica_schema/tests/query_bind_values.rs b/crates/replica_schema/tests/query_bind_values.rs
@@ -1,4 +1,4 @@
-use radroots_replica_schema::farm::FarmQueryBindValues;
+use radroots_replica_schema::farm::{Farm, FarmQueryBindValues};
use radroots_replica_schema::farm_gcs_location::FarmGcsLocationQueryBindValues;
use radroots_replica_schema::farm_member::FarmMemberQueryBindValues;
use radroots_replica_schema::farm_member_claim::FarmMemberClaimQueryBindValues;
@@ -15,7 +15,7 @@ use radroots_replica_schema::plot_tag::PlotTagQueryBindValues;
use radroots_replica_schema::result::{
ReplicaSchemaError, ReplicaSchemaResult, ReplicaSchemaResultList, ReplicaSchemaResultPass,
};
-use radroots_replica_schema::trade_product::TradeProductQueryBindValues;
+use radroots_replica_schema::trade_product::{TradeProduct, TradeProductQueryBindValues};
use serde_json::Value;
macro_rules! assert_query_bind_values {
@@ -386,3 +386,51 @@ fn schema_result_wrappers_cover_constructors_and_status_labels() {
assert_eq!(ReplicaSchemaResultPass::new(true).status_label(), "pass");
assert_eq!(ReplicaSchemaResultPass::new(false).status_label(), "fail");
}
+
+#[test]
+fn checked_in_replica_schema_json_vectors_match_model_behavior() {
+ let vectors =
+ include_str!("../../../contracts/conformance/vectors/replica_schema/json_models.v1.json");
+ let document: Value = serde_json::from_str(vectors).expect("replica schema vector json");
+ let entries = document
+ .get("vectors")
+ .and_then(Value::as_array)
+ .expect("replica schema vectors");
+
+ for entry in entries {
+ let kind = entry.get("kind").and_then(Value::as_str).expect("kind");
+ let input_json = entry
+ .get("input")
+ .and_then(|input| input.get("json"))
+ .expect("input json")
+ .clone();
+ let expected = entry.get("expected").expect("expected");
+ match kind {
+ "replica_schema.farm_json.valid" => {
+ let farm: Farm = serde_json::from_value(input_json).expect("farm json");
+ assert_eq!(
+ farm.id,
+ expected.get("id").and_then(Value::as_str).expect("farm id")
+ );
+ }
+ "replica_schema.farm_json.invalid" => {
+ assert!(serde_json::from_value::<Farm>(input_json).is_err());
+ }
+ "replica_schema.trade_product_json.valid" => {
+ let product: TradeProduct =
+ serde_json::from_value(input_json).expect("trade product json");
+ assert_eq!(
+ product.id,
+ expected
+ .get("id")
+ .and_then(Value::as_str)
+ .expect("trade product id")
+ );
+ }
+ "replica_schema.trade_product_json.invalid" => {
+ assert!(serde_json::from_value::<TradeProduct>(input_json).is_err());
+ }
+ other => panic!("unknown replica schema vector kind {other}"),
+ }
+ }
+}
diff --git a/crates/transport/tests/transport.rs b/crates/transport/tests/transport.rs
@@ -10,6 +10,7 @@ use radroots_transport::{
RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel,
RadrootsTransportTargetReceipt, RadrootsTransportTargetSet, RadrootsTransportTargetUri,
};
+use serde_json::Value;
fn opaque_payload() -> RadrootsTransportPayload {
RadrootsTransportPayload::opaque_bytes("transport-test-payload", b"transport payload")
@@ -596,6 +597,56 @@ fn transport_kind_and_target_parsers_cover_negative_edges() {
}
#[test]
+fn checked_in_transport_target_uri_vectors_match_parser_behavior() {
+ let vectors =
+ include_str!("../../../contracts/conformance/vectors/transport/target_uri.v1.json");
+ let document: Value = serde_json::from_str(vectors).expect("transport target vector json");
+ let entries = document
+ .get("vectors")
+ .and_then(Value::as_array)
+ .expect("transport target vectors");
+
+ for entry in entries {
+ let kind = entry.get("kind").and_then(Value::as_str).expect("kind");
+ let raw_uri = entry
+ .get("input")
+ .and_then(|input| input.get("uri"))
+ .and_then(Value::as_str)
+ .expect("input uri");
+ let expected = entry.get("expected").expect("expected");
+ match kind {
+ "transport.target_uri.valid" => {
+ let target = RadrootsTransportTargetUri::parse(raw_uri).expect("target URI");
+ assert_eq!(
+ target.as_str(),
+ expected
+ .get("canonical_uri")
+ .and_then(Value::as_str)
+ .expect("canonical uri")
+ );
+ }
+ "transport.target_uri.invalid" => {
+ assert!(RadrootsTransportTargetUri::parse(raw_uri).is_err());
+ }
+ "transport.nostr_relay_target.valid" => {
+ let target = RadrootsTransportTarget::nostr_relay(raw_uri).expect("relay target");
+ assert_eq!(
+ target.uri.as_str(),
+ expected
+ .get("canonical_uri")
+ .and_then(Value::as_str)
+ .expect("canonical uri")
+ );
+ }
+ "transport.nostr_relay_target.invalid" => {
+ assert!(RadrootsTransportTarget::nostr_relay(raw_uri).is_err());
+ }
+ other => panic!("unknown transport target vector kind {other}"),
+ }
+ }
+}
+
+#[test]
fn reticulum_transport_targets_require_exact_preview_endpoint() {
let target =
RadrootsTransportTarget::reticulum_preview().expect("exact Reticulum preview endpoint");
diff --git a/docs/architecture/embedded_transport_contract.md b/docs/architecture/embedded_transport_contract.md
@@ -0,0 +1,38 @@
+# Embedded Transport Contract
+
+Radroots public transport code separates transport-neutral delivery contracts from concrete
+transport adapters. Public APIs must describe targets, payloads, delivery status, and satisfaction
+policy without smuggling a relay-only model into generic transport surfaces.
+
+## Contract boundaries
+
+`radroots_transport` owns transport-neutral value types:
+
+- `RadrootsTransportKind`
+- `RadrootsTransportTarget`
+- `RadrootsTransportTargetUri`
+- `RadrootsTransportTargetSet`
+- `RadrootsTransportPayload`
+- `RadrootsTransportSatisfactionPolicy`
+- `RadrootsTransportDeliveryReceipt`
+
+Nostr-specific behavior belongs in Nostr-owned crates and NIP-specific modules. Mesh behavior
+belongs in mesh-owned crates. Generic transport code may name Nostr only when it is modeling an
+explicit Nostr target or a Nostr adapter boundary.
+
+## Embedded and preview transports
+
+The Reticulum preview endpoint is intentionally explicit. The only accepted preview endpoint is the
+checked-in `RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI` value, and preview delivery remains unavailable
+for real payload transfer until a concrete transport implementation is added.
+
+Mesh frame CBOR is a source-level contract for local and embedded transport experimentation. The
+MVP frame shape is fixed by `radroots_mesh` tests and conformance vectors, and real payload bytes
+are rejected by the preview policy.
+
+## Malformed input posture
+
+Transport parsers are part of the release validation surface. Malformed transport target URIs,
+unsupported mesh CBOR shapes, payload-bearing preview frames, and invalid replica JSON models must
+fail through checked-in tests and conformance vectors. Runtime callers should depend on those
+fallible constructors and parser results instead of accepting unchecked target strings or raw bytes.
diff --git a/docs/release/crates_io_policy.md b/docs/release/crates_io_policy.md
@@ -0,0 +1,41 @@
+# Crates.io Policy
+
+Public crate publication must use registry-resolved public dependencies. Release preflight must
+fail closed while required DTO tooling is unavailable from crates.io.
+
+## Publishable crate boundary
+
+Crates that declare `publish = ["crates-io"]` are candidates for public registry publication after
+their source, contracts, tests, and dependency graph pass the repo-local release preflight.
+
+Crates that declare `publish = false` are public source crates but are not candidates for registry
+publication from this repo-local policy surface.
+
+## DTO tooling gate
+
+The DTO crates `dto_bindgen` and `dto_bindgen_core` are currently required by public crate surfaces
+that expose generated DTO metadata. Until the required DTO crates are available from crates.io,
+`cargo xtask release preflight` must report that registry availability as a publication blocker.
+
+That blocker must not be bypassed by:
+
+- git dependencies
+- local path dependencies
+- vendored DTO copies
+- retired-name reexports
+- publication-only source rewrites
+- release-only feature gates
+
+The correct state is an explicit preflight failure that names the missing registry dependency.
+
+## Local validation
+
+Run the release gate from the nested repo root:
+
+```bash
+cargo xtask release preflight
+```
+
+If the command fails only because DTO tooling is unavailable from crates.io, record that exact
+blocker in closeout evidence. Any additional failure must be treated as a separate source or
+contract issue and fixed directly.
diff --git a/docs/release/public_validation_model.md b/docs/release/public_validation_model.md
@@ -0,0 +1,50 @@
+# Public Validation Model
+
+This repository treats public source validation as a fail-closed contract. A change is ready for
+public release consideration only when source formatting, crate tests, contract validation,
+conformance vectors, and release preflight all agree with the checked-in contracts.
+
+## Validation surfaces
+
+The public validation surface is source-controlled in this repo:
+
+- `Cargo.toml` defines the workspace members and crate dependency posture.
+- `contracts/**` defines operation metadata, conformance vectors, event contracts, and release
+ contract inputs.
+- `crates/**/tests/**` exercises crate-local public APIs and malformed-input behavior.
+- `tools/xtask/**` owns repo-local contract validation and release preflight commands.
+
+The standard source validation entrypoints are:
+
+```bash
+cargo fmt --all -- --check
+cargo test --workspace --all-features
+cargo xtask contract validate
+cargo xtask release preflight
+```
+
+Focused hardening slices may run narrower crate commands first, but final release consideration
+must return to the workspace and xtask validation surface.
+
+## Conformance vectors
+
+Stable public vectors live under `contracts/conformance/vectors/**`. These files are test inputs,
+not generated output. They are deliberately small and inspectable so malformed input expectations
+remain reviewable in source control.
+
+The malformed corpus currently covers:
+
+- event wire JSON in `contracts/conformance/vectors/event/nip01_wire.v1.json`
+- transport target URI parsing in `contracts/conformance/vectors/transport/target_uri.v1.json`
+- mesh frame CBOR in `contracts/conformance/vectors/mesh/frame_cbor.v1.json`
+- replica schema JSON in `contracts/conformance/vectors/replica_schema/json_models.v1.json`
+
+Each corpus has at least one canonical valid case and malformed cases that must fail through the
+same public parser or model type used by downstream consumers.
+
+## Failure posture
+
+Validation failures are release blockers. The expected DTO crates.io blocker documented in
+`docs/release/crates_io_policy.md` is still a blocker; it is recorded distinctly so release tooling
+does not silently substitute git dependencies, local path dependencies, vendored copies,
+retired-name reexports, or publication-only workarounds.