commit c3c01fcaf71db470c54f8b5b1cf732b166259576
parent acff346c503a474f703e1b2c55acd81887892f4f
Author: triesap <tyson@radroots.org>
Date: Fri, 19 Jun 2026 16:23:06 -0700
runtime: guard deferred payment commands
- freeze payment and settlement operations as pre-runtime deferred stubs
- assert they stay local low-risk and outside signer or relay publish requirements
- preserve typed not-implemented output before runtime config loading
Diffstat:
| M | src/main.rs | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 63 insertions(+), 0 deletions(-)
diff --git a/src/main.rs b/src/main.rs
@@ -760,3 +760,66 @@ fn envelope_exit_code(envelope: &OutputEnvelope) -> ExitCode {
fn operation_config_error(error: OperationAdapterError) -> runtime::RuntimeError {
runtime::RuntimeError::Config(error.to_string())
}
+
+#[cfg(test)]
+mod tests {
+ use std::collections::BTreeSet;
+
+ use super::*;
+ use crate::registry::{ApprovalPolicy, NetworkRequirement, OPERATION_REGISTRY, RiskLevel};
+
+ const DEFERRED_PAYMENT_OPERATION_IDS: &[&str] = &[
+ "order.payment.record",
+ "order.settlement.accept",
+ "order.settlement.reject",
+ ];
+
+ #[test]
+ fn payment_and_settlement_operations_are_pre_runtime_deferred_only() {
+ let actual = OPERATION_REGISTRY
+ .iter()
+ .filter(|operation| {
+ operation.operation_id.starts_with("order.payment.")
+ || operation.operation_id.starts_with("order.settlement.")
+ })
+ .map(|operation| operation.operation_id)
+ .collect::<BTreeSet<_>>();
+ let expected = DEFERRED_PAYMENT_OPERATION_IDS
+ .iter()
+ .copied()
+ .collect::<BTreeSet<_>>();
+
+ assert_eq!(actual, expected);
+
+ for operation_id in DEFERRED_PAYMENT_OPERATION_IDS {
+ let operation = OPERATION_REGISTRY
+ .iter()
+ .find(|operation| operation.operation_id == *operation_id)
+ .expect("deferred payment operation should be registered");
+
+ assert!(is_deferred_payment_operation(operation.operation_id));
+ assert!(!operation.mutates);
+ assert_eq!(operation.approval_policy, ApprovalPolicy::None);
+ assert_eq!(operation.risk_level, RiskLevel::Low);
+ assert_eq!(
+ network_requirement(operation.operation_id),
+ NetworkRequirement::Local
+ );
+ assert!(!requires_local_signer_mode(operation.operation_id));
+ assert!(!requires_nostr_relay_publish_mode(operation.operation_id));
+ }
+ }
+
+ #[test]
+ fn deferred_payment_guard_runs_before_runtime_config_loading() {
+ let source = include_str!("main.rs");
+ let deferred_guard = source
+ .find("if let Err(error) = validate_pre_runtime_request_contract(&request)")
+ .expect("run should validate pre-runtime request contract");
+ let runtime_config = source
+ .find("let config = RuntimeConfig::from_system")
+ .expect("run should load runtime config after pre-runtime guard");
+
+ assert!(deferred_guard < runtime_config);
+ }
+}