newton-core 0.4.16

newton protocol core sdk
/// Determines whether a Rego policy source requires privacy (TEE attestation).
///
/// Scans for references to Newton privacy namespaces via both dot-notation
/// and bracket-notation access patterns:
/// - `newton.identity.*` / `newton["identity"]` — builtin functions for identity data
/// - `newton.confidential.*` / `newton["confidential"]` — builtin functions for confidential data
/// - `data.privacy.*` / `data["privacy"]` — data namespace for ephemeral inline privacy
///
/// Over-classification (false positive) is safe — the operator fetches attestation
/// unnecessarily. Under-classification (false negative) is a slash-bypass — the
/// operator skips attestation for a task the on-chain circuit would classify as privacy.
///
/// The Rego source is content-addressed via `policyCid` (IPFS CID), so this
/// result is immutable per CID — safe to cache forever.
pub fn requires_privacy(rego_source: &str) -> bool {
    // Dot-notation access (standard Rego style)
    rego_source.contains("newton.identity.")
        || rego_source.contains("newton.confidential.")
        || rego_source.contains("data.privacy.")
        // Bracket-notation access (OPA-compatible alternative)
        || rego_source.contains("\"identity\"")
        || rego_source.contains("\"confidential\"")
        || rego_source.contains("\"privacy\"")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn identity_builtin_detected() {
        let source = r#"
package newton.policy
import future.keywords.if
default allow := false
allow if {
    newton.identity.get("status") == "approved"
}
"#;
        assert!(requires_privacy(source));
    }

    #[test]
    fn confidential_builtin_detected() {
        let source = r#"
package newton.policy
default allow := true
deny if {
    newton.confidential.blacklist.contains(input.from)
}
allow := !deny
"#;
        assert!(requires_privacy(source));
    }

    #[test]
    fn ephemeral_data_namespace_detected() {
        let source = r#"
package newton.policy
default allow := false
allow if {
    some addr in data.privacy.allowlist
    addr == input.from
}
"#;
        assert!(requires_privacy(source));
    }

    #[test]
    fn non_privacy_policy_not_detected() {
        let source = r#"
package newton.policy
default allow := false
allow if {
    input.value < 1000
}
"#;
        assert!(!requires_privacy(source));
    }

    #[test]
    fn secrets_only_policy_not_detected() {
        let source = r#"
package newton.policy
default allow := false
allow if {
    response := newton.http.get(input.url)
    response.status_code == 200
}
"#;
        assert!(!requires_privacy(source));
    }

    #[test]
    fn comment_containing_namespace_detected() {
        // A comment mentioning the namespace shouldn't be a false negative —
        // if the policy author wrote the namespace in a comment, the real usage
        // is likely nearby. Over-classification is safe; under-classification is not.
        let source = r#"
package newton.policy
# Uses newton.identity.get for KYC
default allow := false
allow if { input.value < 100 }
"#;
        assert!(requires_privacy(source));
    }

    #[test]
    fn bracket_notation_identity_detected() {
        let source = r#"
package newton.policy
default allow := false
allow if {
    data["identity"]["status"] == "approved"
}
"#;
        assert!(requires_privacy(source));
    }

    #[test]
    fn bracket_notation_privacy_detected() {
        let source = r#"
package newton.policy
default allow := false
allow if {
    some addr in data["privacy"]["allowlist"]
    addr == input.from
}
"#;
        assert!(requires_privacy(source));
    }

    #[test]
    fn bracket_notation_confidential_detected() {
        let source = r#"
package newton.policy
default allow := true
deny if {
    data["confidential"]["blacklist"][input.from]
}
allow := !deny
"#;
        assert!(requires_privacy(source));
    }
}