use content_addressable::ContentAddressable;
use newt_interaction::{
binding::{validate_response, ResponderContext},
decode_definition, decode_instance, decode_response, publish, AssertionKind, Audience,
ChoiceOption, Control, ControlId, ControlKind, ControlValue, Decoded, HostMint, IdempotencyKey,
InteractionDefinition, InteractionInstance, InteractionKind, Nonce, OptionId, Provenance,
Refusal, Requirement, ResponderPolicy, ResponderProvenance, Response, Revision, Scope,
SemanticRole, Submission,
};
use proptest::prelude::*;
fn definition(markdown: &str, label: &str) -> InteractionDefinition {
InteractionDefinition::new(
InteractionKind::Confirm,
markdown,
vec![Control {
id: ControlId::new("decision").expect("a fixed, valid id"),
kind: ControlKind::Choice {
options: vec![
ChoiceOption {
id: OptionId::new("allow").expect("valid"),
role: SemanticRole::Allow,
label: label.to_string(),
key: String::new(),
aliases: Vec::new(),
},
ChoiceOption {
id: OptionId::new("deny").expect("valid"),
role: SemanticRole::Deny,
label: label.to_string(),
key: String::new(),
aliases: Vec::new(),
},
],
},
label: label.to_string(),
requirement: Requirement::Required,
}],
)
}
fn instance(def: &InteractionDefinition, workspace: &str) -> InteractionInstance {
InteractionInstance {
schema: newt_interaction::InstanceTag,
nonce: Nonce::new("1756200000000000000-0f4c1b2e").expect("valid"),
definition: def.definition_id().expect("id"),
revision: Revision::FIRST,
ttl_ticks: 300,
scope: Scope {
workspace_key: workspace.to_string(),
conversation_id: "conv-1".to_string(),
},
responder_policy: ResponderPolicy {
audiences: vec![Audience::Terminal],
requires_assertion: false,
},
provenance: Provenance {
origin: "fuzz".to_string(),
minted_tick: 1,
},
}
}
fn response(def: &InteractionDefinition, inst: &InteractionInstance, subject: &str) -> Response {
Response {
schema: newt_interaction::ResponseTag,
definition: def.definition_id().expect("id"),
instance: inst.instance_id().expect("id"),
revision: Revision::FIRST,
values: vec![Submission {
control: ControlId::new("decision").expect("valid"),
value: ControlValue::Choice {
option: OptionId::new("deny").expect("valid"),
},
}],
idempotency_key: IdempotencyKey::new("k").expect("valid"),
responder_provenance: ResponderProvenance {
kind: AssertionKind::Unauthenticated,
subject: subject.to_string(),
audience: Audience::Terminal,
assertion: None,
},
}
}
proptest! {
#![proptest_config(ProptestConfig { failure_persistence: None, ..ProptestConfig::default() })]
#[test]
fn decoding_arbitrary_bytes_is_total(bytes in prop::collection::vec(any::<u8>(), 0..512)) {
for decoded in [
decode_definition(&bytes).map(|d| matches!(d, Decoded::Known(_))),
decode_instance(&bytes).map(|d| matches!(d, Decoded::Known(_))),
] {
let _ = decoded;
}
if let Ok(Decoded::Known(record)) = decode_response(&bytes) {
prop_assert_eq!(
record.canonical_form().expect("re-encode"),
bytes,
"a Known record did not re-encode to the bytes it arrived as"
);
}
}
#[test]
fn corrupting_a_real_record_never_preserves_its_identity(
index in 0usize..64, xor in 1u8..=255,
) {
let def = definition("⊘ run_command wants to run `bash`", "allow once");
let mut bytes = def.canonical_form().expect("encode");
let id = def.definition_id().expect("id");
let at = index % bytes.len();
bytes[at] ^= xor;
if let Ok(Decoded::Known(record)) = decode_definition(&bytes) {
prop_assert_ne!(
record.definition_id().expect("id"), id,
"a corrupted record kept the original identity"
);
}
}
#[test]
fn author_text_always_moves_the_identity(
left in "\\PC{0,64}", right in "\\PC{0,64}", label in "\\PC{0,32}",
) {
let a = definition(&left, &label).definition_id().expect("id");
let b = definition(&right, &label).definition_id().expect("id");
prop_assert_eq!(a == b, left == right);
}
#[test]
fn a_responder_supplied_string_never_grants_anything(
subject in "\\PC{0,64}", claimed in "\\PC{0,32}",
) {
let def = definition("markdown", "label");
let inst = instance(&def, "ws-real");
let lifecycle = publish(&HostMint::assert_host_authority(), &inst, &def)
.expect("publishes");
let resp = response(&def, &inst, &subject);
let inside = ResponderContext { workspace_key: "ws-real", registered: &[] };
let refused_for_the_handler = matches!(
validate_response(&def, &inst, &lifecycle, &resp, &inside),
Err(Refusal::UnknownAction { .. })
);
prop_assert!(
refused_for_the_handler,
"a responder-supplied subject changed the outcome inside the fence"
);
prop_assume!(claimed != "ws-real");
let outside = ResponderContext { workspace_key: &claimed, registered: &[] };
let refused_for_the_fence = matches!(
validate_response(&def, &inst, &lifecycle, &resp, &outside),
Err(Refusal::WorkspaceMismatch { .. })
);
prop_assert!(
refused_for_the_fence,
"a record's own strings crossed the caller's workspace fence"
);
}
#[test]
fn a_validated_scalar_is_never_silently_rewritten(text in "\\PC{0,64}") {
if let Ok(id) = ControlId::new(&text) {
prop_assert_eq!(id.as_str(), text.as_str());
}
if let Ok(id) = OptionId::new(&text) {
prop_assert_eq!(id.as_str(), text.as_str());
}
}
}
#[test]
fn the_decoder_reaches_known_err_and_unknown() {
let def = definition("markdown", "label");
let bytes = def.canonical_form().expect("encode");
assert!(
matches!(decode_definition(&bytes), Ok(Decoded::Known(_))),
"the decoder never returns Known; the fuzz properties are vacuous"
);
assert!(decode_definition(&[0xff, 0xff, 0xff]).is_err());
assert!(
matches!(decode_response(&bytes), Ok(Decoded::Unknown(_))),
"a definition read as a response should be Unknown, not Known"
);
}
#[test]
fn a_validated_scalar_reaches_both_outcomes() {
assert_eq!(
ControlId::new("decision").expect("accepts").as_str(),
"decision"
);
assert!(ControlId::new("").is_err(), "the empty id was accepted");
}
#[test]
fn the_same_offer_accepts_when_the_host_registers_the_action() {
use newt_interaction::binding::{HandlerId, RegisteredAction};
let def = definition("markdown", "label");
let inst = instance(&def, "ws-real");
let lifecycle = publish(&HostMint::assert_host_authority(), &inst, &def).expect("publishes");
let resp = response(&def, &inst, "operator:anything");
let actions = vec![RegisteredAction {
option: OptionId::new("deny").expect("valid"),
handler: HandlerId::new("gate::deny").expect("valid"),
audiences: vec![Audience::Terminal],
}];
let context = ResponderContext {
workspace_key: "ws-real",
registered: &actions,
};
let accepted = validate_response(&def, &inst, &lifecycle, &resp, &context)
.expect("the offer accepts once the host registers the action");
assert_eq!(accepted.actions.len(), 1);
}