commit 4f35c29f2efd18becab20739af138b7b66d040b8
parent 3f1ea7f2c9560c692831ef09009d259b4c06b426
Author: triesap <tyson@radroots.org>
Date: Mon, 10 Aug 2026 22:06:44 +0000
contracts: reserve RHI evidence policy
- Freeze the sole qualified evidence source and selector.
- Define required-source completion and coverage precedence.
- Bind equal-timestamp cursor and rejected-event behavior.
- Lock canonical policy digest and publication independence.
Diffstat:
3 files changed, 635 insertions(+), 0 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
@@ -34,6 +34,12 @@
- Before editing, read this file, `README`, `Cargo.toml`,
`radroots.lib.source-lock.v1.toml`, the relevant implementation and tests,
and `config.toml` or `flake.nix` when they are in scope.
+- `.radroots-consumer-root` is the standalone source-lock identity and must
+ remain exactly `rhi`. The reserved pre-implementation evidence authority is
+ `contracts/services_hardening/evidence_policy.v1.json`. RHI source and
+ configuration must implement its exact source, selector, cursor, completion,
+ coverage, digest, and publication-independence semantics; prototype source
+ behavior is not permission to reinterpret them.
- Treat checked-in source, tests, configuration, and prototype behavior as
implementation evidence, not permission to preserve behavior that the active
requirement removes.
diff --git a/contracts/services_hardening/evidence_policy.v1.json b/contracts/services_hardening/evidence_policy.v1.json
@@ -0,0 +1,200 @@
+{
+ "schema": "radroots.rhi.evidence-policy-decision.v1",
+ "contract_version": 1,
+ "decision_state": "reserved_preimplementation",
+ "configuration": {
+ "group": "evidence",
+ "required_fields": ["contract", "contract_version", "policy_id", "sources"],
+ "unknown_fields": "reject",
+ "contract": "radroots.rhi.evidence-policy",
+ "contract_version": 1,
+ "policy_id": { "type": "stable_id", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 64, "pattern": "^[a-z][a-z0-9_-]*$" },
+ "sources": { "type": "array", "minimum_items": 1, "maximum_items": 16, "unique_constraints": [["source_id"], ["kind", "relay_id", "selector"]], "canonical_order": "source_id_ascending_utf8" },
+ "source": {
+ "required_fields": ["source_id", "kind", "relay_id", "required", "selector", "deadline_ms", "lookback_seconds", "overlap_seconds"],
+ "unknown_fields": "reject",
+ "source_id": { "type": "stable_id", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 64, "pattern": "^[a-z][a-z0-9_-]*$" },
+ "kind": { "type": "literal", "value": "nostr_relay" },
+ "relay_id": { "type": "stable_relay_id", "reference": "relays[].id", "canonical_url_required": true },
+ "required": { "type": "boolean" },
+ "selector": { "type": "literal", "value": "trade_mutation_lineage_v1" },
+ "deadline_ms": { "type": "integer", "minimum": 100, "maximum": 30000, "unit": "milliseconds" },
+ "lookback_seconds": { "type": "integer", "minimum": 60, "maximum": 2678400, "unit": "seconds" },
+ "overlap_seconds": { "type": "integer", "minimum": 1, "maximum": 86400, "unit": "seconds", "relationship": "overlap_seconds_less_than_or_equal_to_lookback_seconds" }
+ },
+ "implicit_source": false,
+ "implicit_relay": false,
+ "defaults": false
+ },
+ "source_kinds": {
+ "qualified_v1": ["nostr_relay"],
+ "other_source_kind": "unsupported_contract_input",
+ "adapter_without_real_source": "forbidden"
+ },
+ "selector": {
+ "id": "trade_mutation_lineage_v1",
+ "trade_event_contract": {
+ "repository": "https://github.com/radrootslabs/lib",
+ "path": "contracts/architecture/decisions/services_hardening_events.v1.json",
+ "schema": "radroots.services-hardening.event-decisions.v1",
+ "contract_family": "radroots.trade.mutation-index.v1"
+ },
+ "event_kinds": [3470, 3471, 3472, 3473, 3474],
+ "event_kind_meanings": ["proposal", "decision", "revision_proposal", "revision_decision", "cancellation"],
+ "dynamic_filter": { "tag": "#d", "value": "exact_job_trade_id" },
+ "required_structural_tags": ["contract", "d", "x:mutation", "p:buyer", "p:seller"],
+ "nonproposal_additional_tags": ["x:root", "x:parent"],
+ "author_policy": "validate_against_typed_trade_party_and_mutation_contract",
+ "filter_extra_tags": false,
+ "maximum_events_per_result": 4096,
+ "maximum_result_bytes": 8388608
+ },
+ "cursor": {
+ "scope": ["source_id", "selector_id", "policy_digest", "trade_id"],
+ "tuple": ["created_at_unix_seconds", "event_id_lowercase_hex"],
+ "ordering": ["created_at_unix_seconds_ascending", "event_id_lowercase_hex_ascending"],
+ "initial_since": "max_zero_attempt_started_at_utc_minus_lookback_seconds",
+ "resume_since": "max_zero_cursor_created_at_minus_overlap_seconds",
+ "equal_timestamp_safe": true,
+ "deduplicate_by": "verified_event_id",
+ "advance_candidate": "greatest_canonically_admitted_tuple_only",
+ "no_admitted_event": "leave_cursor_unchanged",
+ "rejected_event": "cannot_advance_cursor_or_completion_or_dirty_generation",
+ "commit": "only_with_generation_fenced_reconciliation_commit_after_source_completion"
+ },
+ "completion": {
+ "success_evidence": "nostr_eose_received_before_source_deadline",
+ "eose_scope": "exact_request_identity_and_selector",
+ "timeout": "incomplete_timeout",
+ "disconnect_before_eose": "incomplete_unavailable",
+ "relay_error_before_eose": "incomplete_unavailable",
+ "unsupported_filter_or_protocol": "unsupported",
+ "bounded_result_exceeded": "incomplete_resource_limit",
+ "invalid_events_before_eose": "completion_may_succeed_but_invalid_events_are_rejected",
+ "eose_after_deadline": "incomplete_timeout",
+ "response_without_eose": "incomplete_unknown"
+ },
+ "required_optional_semantics": {
+ "coverage_precedence": ["Unsupported", "ScopeSatisfied", "Partial", "Missing"],
+ "Unsupported": { "when": "any_required_source_is_unsupported", "allowed_outcomes": ["Indeterminate"] },
+ "ScopeSatisfied": { "when": "every_required_source_completed_and_all_required_scope_prerequisites_are_satisfied", "allowed_outcomes": ["Valid", "Invalid", "Indeterminate"] },
+ "Partial": { "when": "not_unsupported_or_scope_satisfied_and_any_required_or_optional_source_completed_or_any_relevant_evidence_was_admitted", "allowed_outcomes": ["Indeterminate"] },
+ "Missing": { "when": "not_unsupported_and_no_required_or_optional_source_completed_and_no_relevant_evidence_was_admitted", "allowed_outcomes": ["Indeterminate"] },
+ "optional_incomplete": "record_safe_source_result_without_blocking_scope_satisfied",
+ "optional_complete": "include_admitted_evidence_without_substituting_for_required_source",
+ "zero_required_sources": "invalid_configuration"
+ },
+ "policy_digest": {
+ "algorithm": "sha256",
+ "encoding": "lowercase_hex",
+ "domain": "radroots:rhi-evidence-policy:v1",
+ "preimage": "domain_utf8_then_0x00_then_rfc8785_jcs_normalized_policy_utf8",
+ "normalized_policy_fields": ["contract", "contract_version", "policy_id", "sources"],
+ "normalized_source_fields": ["source_id", "kind", "relay_id", "relay_url", "required", "selector", "deadline_ms", "lookback_seconds", "overlap_seconds", "completion"],
+ "source_order": "source_id_ascending_utf8",
+ "relay_url": "resolved_canonical_url_from_relay_id",
+ "completion_value": "nostr_eose_before_deadline"
+ },
+ "publication_relationship": {
+ "relationship": "independent",
+ "evidence_sources_are_publication_targets": false,
+ "publication_targets_affect_coverage_or_outcome": false,
+ "publication_failure_changes_report_or_attestation_content": false,
+ "required_publication_failure": "publication_workflow_and_readiness_only",
+ "disabled_publication": "no_target_or_network_work"
+ },
+ "fixed_vector": {
+ "normalized_policy": {
+ "contract": "radroots.rhi.evidence-policy",
+ "contract_version": 1,
+ "policy_id": "production-primary",
+ "sources": [
+ {
+ "completion": "nostr_eose_before_deadline",
+ "deadline_ms": 10000,
+ "kind": "nostr_relay",
+ "lookback_seconds": 86400,
+ "overlap_seconds": 300,
+ "relay_id": "relay-primary",
+ "relay_url": "wss://relay.example.com/",
+ "required": true,
+ "selector": "trade_mutation_lineage_v1",
+ "source_id": "trade-primary"
+ }
+ ]
+ },
+ "canonical_policy_utf8": "{\"contract\":\"radroots.rhi.evidence-policy\",\"contract_version\":1,\"policy_id\":\"production-primary\",\"sources\":[{\"completion\":\"nostr_eose_before_deadline\",\"deadline_ms\":10000,\"kind\":\"nostr_relay\",\"lookback_seconds\":86400,\"overlap_seconds\":300,\"relay_id\":\"relay-primary\",\"relay_url\":\"wss://relay.example.com/\",\"required\":true,\"selector\":\"trade_mutation_lineage_v1\",\"source_id\":\"trade-primary\"}]}",
+ "preimage_utf8": "radroots:rhi-evidence-policy:v1\u0000{\"contract\":\"radroots.rhi.evidence-policy\",\"contract_version\":1,\"policy_id\":\"production-primary\",\"sources\":[{\"completion\":\"nostr_eose_before_deadline\",\"deadline_ms\":10000,\"kind\":\"nostr_relay\",\"lookback_seconds\":86400,\"overlap_seconds\":300,\"relay_id\":\"relay-primary\",\"relay_url\":\"wss://relay.example.com/\",\"required\":true,\"selector\":\"trade_mutation_lineage_v1\",\"source_id\":\"trade-primary\"}]}",
+ "policy_digest": "43f083e29fcff4f90e66b546c49f74b0205ecb148beb352adb5a211d3e5f86b2",
+ "positive": ["required_eose_before_deadline", "optional_timeout_does_not_block_required_scope"]
+ },
+ "configuration_fixture": {
+ "contract": "radroots.rhi.evidence-policy",
+ "contract_version": 1,
+ "policy_id": "production-primary",
+ "sources": [
+ { "source_id": "trade-primary", "kind": "nostr_relay", "relay_id": "relay-primary", "required": true, "selector": "trade_mutation_lineage_v1", "deadline_ms": 10000, "lookback_seconds": 86400, "overlap_seconds": 300 }
+ ]
+ },
+ "configuration_vectors": [
+ { "case": "valid_base", "mutation": { "op": "none" }, "expected": "valid" },
+ { "case": "invalid_policy_id", "mutation": { "op": "set_top_field", "field": "policy_id", "value": "Invalid Policy" }, "expected": "invalid_policy_id" },
+ { "case": "duplicate_source_id", "mutation": { "op": "append_source", "source": { "source_id": "trade-primary", "kind": "nostr_relay", "relay_id": "relay-secondary", "required": true, "selector": "trade_mutation_lineage_v1", "deadline_ms": 10000, "lookback_seconds": 86400, "overlap_seconds": 300 } }, "expected": "duplicate_source_id" },
+ { "case": "duplicate_binding", "mutation": { "op": "append_source", "source": { "source_id": "trade-alias", "kind": "nostr_relay", "relay_id": "relay-primary", "required": true, "selector": "trade_mutation_lineage_v1", "deadline_ms": 10000, "lookback_seconds": 86400, "overlap_seconds": 300 } }, "expected": "duplicate_source_binding" },
+ { "case": "zero_required", "mutation": { "op": "set_source_field", "field": "required", "value": false }, "expected": "no_required_source" },
+ { "case": "implicit_source", "mutation": { "op": "remove_top_field", "field": "sources" }, "expected": "missing_required_field" },
+ { "case": "zero_sources", "mutation": { "op": "set_top_field", "field": "sources", "value": [] }, "expected": "source_count_out_of_range" },
+ { "case": "seventeen_sources", "mutation": { "op": "repeat_sources", "count": 17 }, "expected": "source_count_out_of_range" },
+ { "case": "non_array_sources", "mutation": { "op": "set_top_field", "field": "sources", "value": "relay-primary" }, "expected": "invalid_sources_type" },
+ { "case": "non_object_source", "mutation": { "op": "set_top_field", "field": "sources", "value": [42] }, "expected": "invalid_source_type" },
+ { "case": "unknown_top_field", "mutation": { "op": "add_top_field", "field": "default_source", "value": "relay-primary" }, "expected": "unknown_field" },
+ { "case": "unknown_source_field", "mutation": { "op": "add_source_field", "field": "default", "value": true }, "expected": "unknown_field" },
+ { "case": "missing_deadline", "mutation": { "op": "remove_source_field", "field": "deadline_ms" }, "expected": "missing_required_field" },
+ { "case": "missing_lookback", "mutation": { "op": "remove_source_field", "field": "lookback_seconds" }, "expected": "missing_required_field" },
+ { "case": "missing_overlap", "mutation": { "op": "remove_source_field", "field": "overlap_seconds" }, "expected": "missing_required_field" },
+ { "case": "unknown_source_kind", "mutation": { "op": "set_source_field", "field": "kind", "value": "filesystem" }, "expected": "unsupported_source_kind" },
+ { "case": "publication_target_as_evidence", "mutation": { "op": "set_source_field", "field": "kind", "value": "publication_target" }, "expected": "unsupported_source_kind" },
+ { "case": "invalid_selector", "mutation": { "op": "set_source_field", "field": "selector", "value": "all_events_v1" }, "expected": "unsupported_selector" },
+ { "case": "invalid_source_id", "mutation": { "op": "set_source_field", "field": "source_id", "value": "Invalid Source" }, "expected": "invalid_source_id" },
+ { "case": "invalid_relay_id", "mutation": { "op": "set_source_field", "field": "relay_id", "value": "/relay" }, "expected": "invalid_source_id" },
+ { "case": "non_boolean_required", "mutation": { "op": "set_source_field", "field": "required", "value": "true" }, "expected": "invalid_required_type" },
+ { "case": "non_integer_deadline", "mutation": { "op": "set_source_field", "field": "deadline_ms", "value": "10000" }, "expected": "invalid_deadline_type" },
+ { "case": "non_integer_lookback", "mutation": { "op": "set_source_field", "field": "lookback_seconds", "value": "86400" }, "expected": "invalid_lookback_type" },
+ { "case": "non_integer_overlap", "mutation": { "op": "set_source_field", "field": "overlap_seconds", "value": "300" }, "expected": "invalid_overlap_type" },
+ { "case": "deadline_minimum", "mutation": { "op": "set_source_field", "field": "deadline_ms", "value": 100 }, "expected": "valid" },
+ { "case": "deadline_below_minimum", "mutation": { "op": "set_source_field", "field": "deadline_ms", "value": 99 }, "expected": "deadline_out_of_range" },
+ { "case": "deadline_maximum", "mutation": { "op": "set_source_field", "field": "deadline_ms", "value": 30000 }, "expected": "valid" },
+ { "case": "deadline_above_maximum", "mutation": { "op": "set_source_field", "field": "deadline_ms", "value": 30001 }, "expected": "deadline_out_of_range" },
+ { "case": "lookback_minimum", "mutation": { "op": "set_source_fields", "fields": { "lookback_seconds": 60, "overlap_seconds": 60 } }, "expected": "valid" },
+ { "case": "lookback_below_minimum", "mutation": { "op": "set_source_field", "field": "lookback_seconds", "value": 59 }, "expected": "lookback_out_of_range" },
+ { "case": "lookback_maximum", "mutation": { "op": "set_source_field", "field": "lookback_seconds", "value": 2678400 }, "expected": "valid" },
+ { "case": "lookback_above_maximum", "mutation": { "op": "set_source_field", "field": "lookback_seconds", "value": 2678401 }, "expected": "lookback_out_of_range" },
+ { "case": "overlap_minimum", "mutation": { "op": "set_source_field", "field": "overlap_seconds", "value": 1 }, "expected": "valid" },
+ { "case": "overlap_below_minimum", "mutation": { "op": "set_source_field", "field": "overlap_seconds", "value": 0 }, "expected": "overlap_out_of_range" },
+ { "case": "overlap_maximum", "mutation": { "op": "set_source_field", "field": "overlap_seconds", "value": 86400 }, "expected": "valid" },
+ { "case": "overlap_above_maximum", "mutation": { "op": "set_source_field", "field": "overlap_seconds", "value": 86401 }, "expected": "overlap_out_of_range" },
+ { "case": "overlap_exceeds_lookback", "mutation": { "op": "set_source_fields", "fields": { "lookback_seconds": 60, "overlap_seconds": 61 } }, "expected": "overlap_exceeds_lookback" }
+ ],
+ "completion_vectors": [
+ { "case": "eose_before_deadline", "input": { "eose_at_ms": 9999, "deadline_ms": 10000, "disconnect": false, "relay_error": false, "unsupported": false, "resource_limit": false }, "expected": "complete" },
+ { "case": "eose_at_deadline", "input": { "eose_at_ms": 10000, "deadline_ms": 10000, "disconnect": false, "relay_error": false, "unsupported": false, "resource_limit": false }, "expected": "incomplete_timeout" },
+ { "case": "missing_eose", "input": { "deadline_elapsed_without_eose": true, "deadline_ms": 10000, "disconnect": false, "relay_error": false, "unsupported": false, "resource_limit": false }, "expected": "incomplete_timeout" },
+ { "case": "eose_after_deadline", "input": { "eose_at_ms": 10001, "deadline_ms": 10000, "disconnect": false, "relay_error": false, "unsupported": false, "resource_limit": false }, "expected": "incomplete_timeout" },
+ { "case": "disconnect_before_eose", "input": { "deadline_ms": 10000, "disconnect": true, "relay_error": false, "unsupported": false, "resource_limit": false }, "expected": "incomplete_unavailable" },
+ { "case": "relay_error_before_eose", "input": { "deadline_ms": 10000, "disconnect": false, "relay_error": true, "unsupported": false, "resource_limit": false }, "expected": "incomplete_unavailable" },
+ { "case": "unsupported", "input": { "deadline_ms": 10000, "disconnect": false, "relay_error": false, "unsupported": true, "resource_limit": false }, "expected": "unsupported" },
+ { "case": "bounded_result_exceeded", "input": { "deadline_ms": 10000, "disconnect": false, "relay_error": false, "unsupported": false, "resource_limit": true }, "expected": "incomplete_resource_limit" },
+ { "case": "response_without_eose", "input": { "deadline_ms": 10000, "disconnect": false, "relay_error": false, "unsupported": false, "resource_limit": false }, "expected": "incomplete_unknown" }
+ ],
+ "cursor_vectors": [
+ { "case": "rejected_event_cannot_advance", "current": { "created_at_unix_seconds": 100, "event_id_lowercase_hex": "0000000000000000000000000000000000000000000000000000000000000001" }, "candidate": { "created_at_unix_seconds": 101, "event_id_lowercase_hex": "0000000000000000000000000000000000000000000000000000000000000002", "admitted": false }, "source_complete": true, "generation_fence": true, "expected": { "created_at_unix_seconds": 100, "event_id_lowercase_hex": "0000000000000000000000000000000000000000000000000000000000000001" } },
+ { "case": "equal_timestamp_event_id_advances", "current": { "created_at_unix_seconds": 100, "event_id_lowercase_hex": "0000000000000000000000000000000000000000000000000000000000000001" }, "candidate": { "created_at_unix_seconds": 100, "event_id_lowercase_hex": "0000000000000000000000000000000000000000000000000000000000000002", "admitted": true }, "source_complete": true, "generation_fence": true, "expected": { "created_at_unix_seconds": 100, "event_id_lowercase_hex": "0000000000000000000000000000000000000000000000000000000000000002" } }
+ ],
+ "coverage_vectors": [
+ { "case": "required_unsupported", "sources": [{ "required": true, "completion": "unsupported", "admitted_events": 0 }], "scope_prerequisites_satisfied": false, "expected_coverage": "Unsupported", "allowed_outcomes": ["Indeterminate"] },
+ { "case": "nothing_observed", "sources": [{ "required": true, "completion": "incomplete", "admitted_events": 0 }], "scope_prerequisites_satisfied": false, "expected_coverage": "Missing", "allowed_outcomes": ["Indeterminate"] },
+ { "case": "optional_evidence_required_incomplete", "sources": [{ "required": true, "completion": "incomplete", "admitted_events": 0 }, { "required": false, "completion": "complete", "admitted_events": 1 }], "scope_prerequisites_satisfied": false, "expected_coverage": "Partial", "allowed_outcomes": ["Indeterminate"] },
+ { "case": "required_complete_prerequisite_missing", "sources": [{ "required": true, "completion": "complete", "admitted_events": 1 }], "scope_prerequisites_satisfied": false, "expected_coverage": "Partial", "allowed_outcomes": ["Indeterminate"] },
+ { "case": "required_scope_satisfied", "sources": [{ "required": true, "completion": "complete", "admitted_events": 1 }, { "required": false, "completion": "incomplete", "admitted_events": 0 }], "scope_prerequisites_satisfied": true, "expected_coverage": "ScopeSatisfied", "allowed_outcomes": ["Valid", "Invalid", "Indeterminate"] }
+ ]
+}
diff --git a/tests/services_hardening_evidence_policy.rs b/tests/services_hardening_evidence_policy.rs
@@ -0,0 +1,429 @@
+#![forbid(unsafe_code)]
+
+use serde_json::Value;
+use sha2::{Digest, Sha256};
+use std::collections::BTreeSet;
+
+const POLICY: &str = include_str!("../contracts/services_hardening/evidence_policy.v1.json");
+const CONSUMER_ROOT: &str = include_str!("../.radroots-consumer-root");
+
+fn policy() -> Value {
+ serde_json::from_str(POLICY).expect("evidence policy decision must be valid JSON")
+}
+
+fn sha256_hex(bytes: &[u8]) -> String {
+ Sha256::digest(bytes)
+ .iter()
+ .map(|byte| format!("{byte:02x}"))
+ .collect()
+}
+
+fn decision_sections_digest(value: &Value) -> String {
+ let sections = serde_json::json!({
+ "completion": value["completion"],
+ "configuration": value["configuration"],
+ "configuration_fixture": value["configuration_fixture"],
+ "cursor": value["cursor"],
+ "fixed_vector": value["fixed_vector"],
+ "policy_digest": value["policy_digest"],
+ "publication_relationship": value["publication_relationship"],
+ "required_optional_semantics": value["required_optional_semantics"],
+ "selector": value["selector"],
+ "source_kinds": value["source_kinds"],
+ "configuration_vectors": value["configuration_vectors"],
+ "completion_vectors": value["completion_vectors"],
+ "cursor_vectors": value["cursor_vectors"],
+ "coverage_vectors": value["coverage_vectors"]
+ });
+ sha256_hex(&serde_json::to_vec(§ions).expect("serialize decision sections"))
+}
+
+fn stable_id(value: &str) -> bool {
+ !value.is_empty()
+ && value.len() <= 64
+ && value.as_bytes()[0].is_ascii_lowercase()
+ && value
+ .bytes()
+ .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"_-".contains(&byte))
+}
+
+fn mutate_config(base: &Value, mutation: &Value) -> Value {
+ let mut config = base.clone();
+ let object = config.as_object_mut().unwrap();
+ match mutation["op"].as_str().unwrap() {
+ "none" => {}
+ "append_source" => object["sources"]
+ .as_array_mut()
+ .unwrap()
+ .push(mutation["source"].clone()),
+ "set_source_field" => {
+ object["sources"][0][mutation["field"].as_str().unwrap()] = mutation["value"].clone();
+ }
+ "set_source_fields" => {
+ for (field, value) in mutation["fields"].as_object().unwrap() {
+ object["sources"][0][field] = value.clone();
+ }
+ }
+ "remove_top_field" => {
+ object.remove(mutation["field"].as_str().unwrap());
+ }
+ "set_top_field" => {
+ object.insert(
+ mutation["field"].as_str().unwrap().to_owned(),
+ mutation["value"].clone(),
+ );
+ }
+ "add_top_field" => {
+ object.insert(
+ mutation["field"].as_str().unwrap().to_owned(),
+ mutation["value"].clone(),
+ );
+ }
+ "repeat_sources" => {
+ let template = object["sources"][0].clone();
+ let count = mutation["count"].as_u64().unwrap();
+ let sources = (0..count)
+ .map(|index| {
+ let mut source = template.clone();
+ source["source_id"] = format!("trade-{index}").into();
+ source["relay_id"] = format!("relay-{index}").into();
+ source
+ })
+ .collect();
+ object.insert("sources".to_owned(), Value::Array(sources));
+ }
+ "remove_source_field" => {
+ object["sources"][0]
+ .as_object_mut()
+ .unwrap()
+ .remove(mutation["field"].as_str().unwrap());
+ }
+ "add_source_field" => {
+ object["sources"][0].as_object_mut().unwrap().insert(
+ mutation["field"].as_str().unwrap().to_owned(),
+ mutation["value"].clone(),
+ );
+ }
+ operation => panic!("unknown configuration mutation {operation}"),
+ }
+ config
+}
+
+fn validate_config(config: &Value) -> Result<(), &'static str> {
+ let Some(object) = config.as_object() else {
+ return Err("invalid_config_type");
+ };
+ let required_top = BTreeSet::from(["contract", "contract_version", "policy_id", "sources"]);
+ let actual_top = object.keys().map(String::as_str).collect::<BTreeSet<_>>();
+ if !required_top.is_subset(&actual_top) {
+ return Err("missing_required_field");
+ }
+ if actual_top != required_top {
+ return Err("unknown_field");
+ }
+ if config["contract"] != "radroots.rhi.evidence-policy" || config["contract_version"] != 1 {
+ return Err("invalid_contract_identity");
+ }
+ if !config["policy_id"].as_str().is_some_and(stable_id) {
+ return Err("invalid_policy_id");
+ }
+ let Some(sources) = config["sources"].as_array() else {
+ return Err("invalid_sources_type");
+ };
+ if sources.is_empty() || sources.len() > 16 {
+ return Err("source_count_out_of_range");
+ }
+ let required_source = BTreeSet::from([
+ "deadline_ms",
+ "kind",
+ "lookback_seconds",
+ "overlap_seconds",
+ "relay_id",
+ "required",
+ "selector",
+ "source_id",
+ ]);
+ for source in sources {
+ let Some(source) = source.as_object() else {
+ return Err("invalid_source_type");
+ };
+ let actual = source.keys().map(String::as_str).collect::<BTreeSet<_>>();
+ if !required_source.is_subset(&actual) {
+ return Err("missing_required_field");
+ }
+ if actual != required_source {
+ return Err("unknown_field");
+ }
+ if source["kind"] != "nostr_relay" {
+ return Err("unsupported_source_kind");
+ }
+ if source["selector"] != "trade_mutation_lineage_v1" {
+ return Err("unsupported_selector");
+ }
+ if !source["source_id"].as_str().is_some_and(stable_id)
+ || !source["relay_id"].as_str().is_some_and(stable_id)
+ {
+ return Err("invalid_source_id");
+ }
+ if !source["required"].is_boolean() {
+ return Err("invalid_required_type");
+ }
+ let deadline = source["deadline_ms"]
+ .as_u64()
+ .ok_or("invalid_deadline_type")?;
+ if !(100..=30_000).contains(&deadline) {
+ return Err("deadline_out_of_range");
+ }
+ let lookback = source["lookback_seconds"]
+ .as_u64()
+ .ok_or("invalid_lookback_type")?;
+ if !(60..=2_678_400).contains(&lookback) {
+ return Err("lookback_out_of_range");
+ }
+ let overlap = source["overlap_seconds"]
+ .as_u64()
+ .ok_or("invalid_overlap_type")?;
+ if !(1..=86_400).contains(&overlap) {
+ return Err("overlap_out_of_range");
+ }
+ if overlap > lookback {
+ return Err("overlap_exceeds_lookback");
+ }
+ }
+ let source_ids = sources
+ .iter()
+ .map(|source| source["source_id"].as_str().unwrap())
+ .collect::<BTreeSet<_>>();
+ if source_ids.len() != sources.len() {
+ return Err("duplicate_source_id");
+ }
+ let bindings = sources
+ .iter()
+ .map(|source| {
+ format!(
+ "{}|{}|{}",
+ source["kind"].as_str().unwrap(),
+ source["relay_id"].as_str().unwrap(),
+ source["selector"].as_str().unwrap()
+ )
+ })
+ .collect::<BTreeSet<_>>();
+ if bindings.len() != sources.len() {
+ return Err("duplicate_source_binding");
+ }
+ if !sources.iter().any(|source| source["required"] == true) {
+ return Err("no_required_source");
+ }
+ Ok(())
+}
+
+fn completion(input: &Value) -> &'static str {
+ if input["unsupported"] == true {
+ "unsupported"
+ } else if input["resource_limit"] == true {
+ "incomplete_resource_limit"
+ } else if input["disconnect"] == true || input["relay_error"] == true {
+ "incomplete_unavailable"
+ } else if let Some(eose_at) = input["eose_at_ms"].as_u64() {
+ if eose_at < input["deadline_ms"].as_u64().unwrap() {
+ "complete"
+ } else {
+ "incomplete_timeout"
+ }
+ } else if input["deadline_elapsed_without_eose"] == true {
+ "incomplete_timeout"
+ } else {
+ "incomplete_unknown"
+ }
+}
+
+fn coverage(vector: &Value) -> (&'static str, Vec<&'static str>) {
+ let sources = vector["sources"].as_array().unwrap();
+ if sources
+ .iter()
+ .any(|source| source["required"] == true && source["completion"] == "unsupported")
+ {
+ return ("Unsupported", vec!["Indeterminate"]);
+ }
+ let all_required_complete = sources
+ .iter()
+ .filter(|source| source["required"] == true)
+ .all(|source| source["completion"] == "complete");
+ if all_required_complete && vector["scope_prerequisites_satisfied"] == true {
+ return ("ScopeSatisfied", vec!["Valid", "Invalid", "Indeterminate"]);
+ }
+ if sources.iter().any(|source| {
+ source["completion"] == "complete" || source["admitted_events"].as_u64().unwrap() > 0
+ }) {
+ ("Partial", vec!["Indeterminate"])
+ } else {
+ ("Missing", vec!["Indeterminate"])
+ }
+}
+
+#[test]
+fn policy_identity_source_and_selector_are_exact() {
+ assert_eq!(CONSUMER_ROOT, "rhi\n");
+ let value = policy();
+ assert_eq!(value["schema"], "radroots.rhi.evidence-policy-decision.v1");
+ assert_eq!(value["contract_version"], 1);
+ assert_eq!(value["decision_state"], "reserved_preimplementation");
+ assert_eq!(
+ value["source_kinds"]["qualified_v1"],
+ serde_json::json!(["nostr_relay"])
+ );
+ assert_eq!(
+ value["selector"],
+ serde_json::json!({
+ "id": "trade_mutation_lineage_v1",
+ "trade_event_contract": {
+ "repository": "https://github.com/radrootslabs/lib",
+ "path": "contracts/architecture/decisions/services_hardening_events.v1.json",
+ "schema": "radroots.services-hardening.event-decisions.v1",
+ "contract_family": "radroots.trade.mutation-index.v1"
+ },
+ "event_kinds": [3470, 3471, 3472, 3473, 3474],
+ "event_kind_meanings": ["proposal", "decision", "revision_proposal", "revision_decision", "cancellation"],
+ "dynamic_filter": { "tag": "#d", "value": "exact_job_trade_id" },
+ "required_structural_tags": ["contract", "d", "x:mutation", "p:buyer", "p:seller"],
+ "nonproposal_additional_tags": ["x:root", "x:parent"],
+ "author_policy": "validate_against_typed_trade_party_and_mutation_contract",
+ "filter_extra_tags": false,
+ "maximum_events_per_result": 4096,
+ "maximum_result_bytes": 8_388_608
+ })
+ );
+}
+
+#[test]
+fn cursor_completion_and_coverage_fail_closed() {
+ let value = policy();
+ assert_eq!(
+ value["cursor"]["tuple"],
+ serde_json::json!(["created_at_unix_seconds", "event_id_lowercase_hex"])
+ );
+ assert_eq!(value["cursor"]["equal_timestamp_safe"], true);
+ assert_eq!(
+ value["cursor"]["rejected_event"],
+ "cannot_advance_cursor_or_completion_or_dirty_generation"
+ );
+ assert_eq!(
+ value["completion"]["success_evidence"],
+ "nostr_eose_received_before_source_deadline"
+ );
+ assert_eq!(
+ value["completion"]["response_without_eose"],
+ "incomplete_unknown"
+ );
+ assert_eq!(
+ value["required_optional_semantics"]["coverage_precedence"],
+ serde_json::json!(["Unsupported", "ScopeSatisfied", "Partial", "Missing"])
+ );
+ assert_eq!(
+ value["required_optional_semantics"]["optional_incomplete"],
+ "record_safe_source_result_without_blocking_scope_satisfied"
+ );
+ let base = &value["configuration_fixture"];
+ for vector in value["configuration_vectors"].as_array().unwrap() {
+ let candidate = mutate_config(base, &vector["mutation"]);
+ let actual = validate_config(&candidate)
+ .map(|()| "valid")
+ .unwrap_or_else(|error| error);
+ assert_eq!(
+ actual,
+ vector["expected"].as_str().unwrap(),
+ "configuration vector {}",
+ vector["case"]
+ );
+ }
+ for vector in value["completion_vectors"].as_array().unwrap() {
+ assert_eq!(
+ completion(&vector["input"]),
+ vector["expected"].as_str().unwrap(),
+ "completion vector {}",
+ vector["case"]
+ );
+ }
+ for vector in value["cursor_vectors"].as_array().unwrap() {
+ let current = &vector["current"];
+ let candidate = &vector["candidate"];
+ let next = if candidate["admitted"] == true
+ && vector["source_complete"] == true
+ && vector["generation_fence"] == true
+ && (
+ candidate["created_at_unix_seconds"].as_u64(),
+ candidate["event_id_lowercase_hex"].as_str(),
+ ) > (
+ current["created_at_unix_seconds"].as_u64(),
+ current["event_id_lowercase_hex"].as_str(),
+ ) {
+ candidate
+ } else {
+ current
+ };
+ let projected = serde_json::json!({
+ "created_at_unix_seconds": next["created_at_unix_seconds"],
+ "event_id_lowercase_hex": next["event_id_lowercase_hex"]
+ });
+ assert_eq!(
+ projected, vector["expected"],
+ "cursor vector {}",
+ vector["case"]
+ );
+ }
+ for vector in value["coverage_vectors"].as_array().unwrap() {
+ let (actual_coverage, actual_outcomes) = coverage(vector);
+ assert_eq!(actual_coverage, vector["expected_coverage"]);
+ assert_eq!(
+ actual_outcomes,
+ vector["allowed_outcomes"]
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|outcome| outcome.as_str().unwrap())
+ .collect::<Vec<_>>()
+ );
+ }
+}
+
+#[test]
+fn policy_digest_vector_and_publication_independence_are_exact() {
+ let value = policy();
+ let vector = &value["fixed_vector"]["normalized_policy"];
+ let canonical = serde_json::to_vec(vector).expect("canonical vector JSON");
+ assert_eq!(
+ canonical,
+ value["fixed_vector"]["canonical_policy_utf8"]
+ .as_str()
+ .unwrap()
+ .as_bytes()
+ );
+ let mut preimage = b"radroots:rhi-evidence-policy:v1\0".to_vec();
+ preimage.extend_from_slice(&canonical);
+ assert_eq!(
+ preimage,
+ value["fixed_vector"]["preimage_utf8"]
+ .as_str()
+ .unwrap()
+ .as_bytes()
+ );
+ assert_eq!(
+ sha256_hex(&preimage),
+ value["fixed_vector"]["policy_digest"].as_str().unwrap()
+ );
+ assert_eq!(
+ value["publication_relationship"],
+ serde_json::json!({
+ "relationship": "independent",
+ "evidence_sources_are_publication_targets": false,
+ "publication_targets_affect_coverage_or_outcome": false,
+ "publication_failure_changes_report_or_attestation_content": false,
+ "required_publication_failure": "publication_workflow_and_readiness_only",
+ "disabled_publication": "no_target_or_network_work"
+ })
+ );
+ assert_eq!(
+ decision_sections_digest(&value),
+ "242642269d9272d2a1bae50bae5a02a1329cab1a2dfb636d363aa362ce475011"
+ );
+}