         // Note that sometimes we also need to make changes to the meta/global
         // itself -- e.g., missing declined. That should be a method on MetaGlobal.
         return self
+    }
+}
+
+public func ==(lhs: EngineConfiguration, rhs: EngineConfiguration) -> Bool {
+    return Set(lhs.enabled) == Set(rhs.enabled)
+}
+
+extension EngineConfiguration: CustomStringConvertible {
+    public var description: String {
+        return "EngineConfiguration(enabled: \(self.enabled.sort()), declined: \(self.declined.sort()))"
     }
 }
 

        ])
        return CleartextPayloadJSON(json)
    }
}

public func ==(lhs: MetaGlobal, rhs: MetaGlobal) -> Bool {

// Note that EngineConfiguration is not enough to evolve an existing meta/global:
// a meta/global generated from this will have different syncIDs and will
// always use this device's engine versions.
public class EngineConfiguration: Equatable {
    let enabled: [String]
    let declined: [String]
    public init(enabled: [String], declined: [String]) {
