From e6ea518e89fde3e4a4aebca198f2a9211193759c Mon Sep 17 00:00:00 2001 From: Antoine Grondin Date: Fri, 22 Aug 2025 18:05:48 +0900 Subject: [PATCH] hashing arbitrary valuers --- go/go.mod | 4 +- go/types/v1/fingerprint.go | 569 ++++++++++++++++++++++++++++++-- go/types/v1/fingerprint_test.go | 497 ++++++++++++++++++++++++++++ go/types/v1/otel_resource.go | 4 +- go/types/v1/otel_scope.go | 6 +- 5 files changed, 1045 insertions(+), 35 deletions(-) create mode 100644 go/types/v1/fingerprint_test.go diff --git a/go/go.mod b/go/go.mod index e76068d..7340084 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,8 +1,6 @@ module github.com/humanlogio/api/go -go 1.23.0 - -toolchain go1.24.3 +go 1.25 require ( connectrpc.com/connect v1.16.2 diff --git a/go/types/v1/fingerprint.go b/go/types/v1/fingerprint.go index 6988c78..b2d4108 100644 --- a/go/types/v1/fingerprint.go +++ b/go/types/v1/fingerprint.go @@ -2,6 +2,7 @@ package typesv1 import ( "fmt" + "iter" "slices" "github.com/cespare/xxhash" @@ -34,16 +35,30 @@ var ( tagKVsh = xxhash.Sum64([]byte{tagKVs}) ) +func Hash64Resource(schemaURL string, kvs iter.Seq2[string, Valuer]) uint64 { + h64 := Hash64String(schemaURL) + h64 ^= Hash64AnyKVs_orderDoesntMatter(kvs) + return h64 +} + +func Hash64Scope(schemaURL, name, version string, kvs iter.Seq2[string, Valuer]) uint64 { + h64 := Hash64String(schemaURL) + h64 ^= Hash64String(name) + h64 ^= Hash64String(version) + h64 ^= Hash64AnyKVs_orderDoesntMatter(kvs) + return h64 +} + func Hash64Value(val *Val) uint64 { switch vv := val.Kind.(type) { case *Val_Str: return Hash64String(vv.Str) case *Val_TraceId: - return Hash64TraceID(vv.TraceId) + return Hash64TraceID(vv.TraceId.Raw) case *Val_SpanId: - return Hash64SpanID(vv.SpanId) + return Hash64SpanID(vv.SpanId.Raw) case *Val_Ulid: - return Hash64ULID(vv.Ulid) + return Hash64ULID(vv.Ulid.High, vv.Ulid.Low) case *Val_Blob: return Hash64Blob(vv.Blob) case *Val_Null: @@ -98,40 +113,91 @@ func Hash64Values_orderDoesntMatter(arr []*Val) uint64 { return xored } +func Hash64Any(v Valuer) uint64 { + return v( + Hash64String, + Hash64TraceID, + Hash64SpanID, + Hash64ULID, + Hash64Blob, + Hash64Null, + Hash64Bool, + Hash64I64, + Hash64F64, + Hash64Hash64, + Hash64Ts, + Hash64Dur, + Hash64AnyArray_orderDoesntMatter, + Hash64AnyKVs_orderDoesntMatter, + Hash64AnyMaps_orderDoesntMatter, + ) +} + +func Hash64AnyArray_orderDoesntMatter(items iter.Seq[Valuer]) uint64 { + xored := tagArrayh + for el := range items { + h := Hash64Any(el) + xored ^= h + } + return xored +} + +func Hash64AnyKVs_orderDoesntMatter(kvs iter.Seq2[string, Valuer]) uint64 { + xored := tagKVsh + for k, v := range kvs { + kh := xxhash.Sum64String(k) + vh := Hash64Any(v) + h := kh ^ vh + xored ^= h + } + return xored +} + +func Hash64AnyMaps_orderDoesntMatter(kvs iter.Seq2[Valuer, Valuer]) uint64 { + xored := tagMapEntriesh + for k, v := range kvs { + kh := Hash64Any(k) + vh := Hash64Any(v) + h := kh ^ vh + xored ^= h + } + return xored +} + func Hash64String(v string) uint64 { fp := slices.Concat([]byte{tagStr}, []byte(v)) return xxhash.Sum64(fp) } -func Hash64TraceID(v *TraceID) uint64 { - fp := slices.Concat([]byte{tagTraceID}, v.Raw) +func Hash64TraceID(v []byte) uint64 { + fp := slices.Concat([]byte{tagTraceID}, v) return xxhash.Sum64(fp[:]) } -func Hash64SpanID(v *SpanID) uint64 { - fp := slices.Concat([]byte{tagSpanID}, v.Raw) +func Hash64SpanID(v []byte) uint64 { + fp := slices.Concat([]byte{tagSpanID}, v) return xxhash.Sum64(fp[:]) } -func Hash64ULID(v *ULID) uint64 { +func Hash64ULID(hi, lo uint64) uint64 { fp := [17]byte{ tagULID, - byte(v.High >> 56), - byte(v.High >> 48), - byte(v.High >> 40), - byte(v.High >> 32), - byte(v.High >> 24), - byte(v.High >> 16), - byte(v.High >> 8), - byte(v.High), - byte(v.Low >> 56), - byte(v.Low >> 48), - byte(v.Low >> 40), - byte(v.Low >> 32), - byte(v.Low >> 24), - byte(v.Low >> 16), - byte(v.Low >> 8), - byte(v.Low), + byte(hi >> 56), + byte(hi >> 48), + byte(hi >> 40), + byte(hi >> 32), + byte(hi >> 24), + byte(hi >> 16), + byte(hi >> 8), + byte(hi), + byte(lo >> 56), + byte(lo >> 48), + byte(lo >> 40), + byte(lo >> 32), + byte(lo >> 24), + byte(lo >> 16), + byte(lo >> 8), + byte(lo), } return xxhash.Sum64(fp[:]) } @@ -236,3 +302,458 @@ func Hash64Dur(seconds int64, nanos int32) uint64 { } return xxhash.Sum64(fp[:]) } + +type Valuer func( + onStr func(string) uint64, + onTraceId func([]byte) uint64, + onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs func(seconds int64, nanos int32) uint64, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, +) uint64 + +func KVsToValuer(kvs []*KV) iter.Seq2[string, Valuer] { + return func(yield func(string, Valuer) bool) { + for _, kv := range kvs { + if !yield(kv.Key, ValuerVal(kv.Value)) { + return + } + } + } +} + +func ValsToValuer(vals []*Val) iter.Seq[Valuer] { + return func(yield func(Valuer) bool) { + for _, el := range vals { + if !yield(ValuerVal(el)) { + return + } + } + } +} + +func ObjectToValuer(obj *Obj) iter.Seq2[string, Valuer] { + return KVsToValuer(obj.Kvs) +} + +func MapToValuer(mm *Map) iter.Seq2[Valuer, Valuer] { + return func(yield func(Valuer, Valuer) bool) { + for _, kv := range mm.Entries { + if !yield(ValuerVal(kv.Key), ValuerVal(kv.Value)) { + return + } + } + } +} + +func ValuerVal(val *Val) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + switch vv := val.Kind.(type) { + case *Val_Str: + return onStr(vv.Str) + case *Val_F64: + return onF64(vv.F64) + case *Val_I64: + return onI64(vv.I64) + case *Val_Hash64: + return onHash64(vv.Hash64) + case *Val_Bool: + return onBool(vv.Bool) + case *Val_Ts: + return onTs(vv.Ts.Seconds, vv.Ts.Nanos) + case *Val_Dur: + return onDur(vv.Dur.Seconds, vv.Dur.Nanos) + case *Val_Blob: + return onBlob(vv.Blob) + case *Val_TraceId: + return onTraceId(vv.TraceId.Raw) + case *Val_SpanId: + return onSpanId(vv.SpanId.Raw) + case *Val_Ulid: + return onUlid(vv.Ulid.High, vv.Ulid.Low) + case *Val_Arr: + return onArr(ValsToValuer(vv.Arr.Items)) + case *Val_Obj: + return onObj(ObjectToValuer(vv.Obj)) + case *Val_Map: + return onMap(MapToValuer(vv.Map)) + case *Val_Null: + return onNull() + default: + panic(fmt.Sprintf("missing case: %T (%v)", vv, vv)) + } + } +} + +func ValuerString(v string) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onStr(v) + } +} + +func ValuerTraceID(v []byte) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onTraceId(v) + } +} + +func ValuerSpanID(v []byte) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onSpanId(v) + } +} + +func ValuerULID(hi, lo uint64) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onUlid(hi, lo) + } +} + +func ValuerBlob(v []byte) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onBlob(v) + } +} +func ValuerNull() Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onNull() + } +} + +func ValuerBool(v bool) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onBool(v) + } +} + +func ValuerI64(v int64) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onI64(v) + } +} + +func ValuerF64(v float64) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onF64(v) + } +} + +func ValuerHash64(v uint64) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onHash64(v) + } +} + +func ValuerTimestamp(seconds int64, nanos int32) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onTs(seconds, nanos) + } +} + +func ValuerDuration(seconds int64, nanos int32) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onDur(seconds, nanos) + } +} + +func ValuerIteratorFromSlice(v []Valuer) iter.Seq[Valuer] { + return func(yield func(Valuer) bool) { + for _, el := range v { + if !yield(el) { + return + } + } + } +} + +func ValuerArr(v iter.Seq[Valuer]) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onArr(v) + } +} + +func ValuerIteratorFromObjMap(v map[string]Valuer) iter.Seq2[string, Valuer] { + return func(yield func(string, Valuer) bool) { + for k, v := range v { + if !yield(k, v) { + return + } + } + } +} + +func ValuerObj(v iter.Seq2[string, Valuer]) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onObj(v) + } +} + +type ValuerMapEntry struct { + Key Valuer + Valuer Valuer +} + +func ValuerIteratorFromMap(kvs []ValuerMapEntry) iter.Seq2[Valuer, Valuer] { + return func(yield func(Valuer, Valuer) bool) { + for _, kv := range kvs { + if !yield(kv.Key, kv.Valuer) { + return + } + } + } +} + +func ValuerMap(v iter.Seq2[Valuer, Valuer]) Valuer { + return func( + onStr func(string) uint64, + onTraceId, onSpanId func([]byte) uint64, + onUlid func(hi uint64, lo uint64) uint64, + onBlob func([]byte) uint64, + onNull func() uint64, + onBool func(bool) uint64, + onI64 func(int64) uint64, + onF64 func(float64) uint64, + onHash64 func(uint64) uint64, + onTs, + onDur func(seconds int64, nanos int32) uint64, + onArr func(iter.Seq[Valuer]) uint64, + onObj func(iter.Seq2[string, Valuer]) uint64, + onMap func(iter.Seq2[Valuer, Valuer]) uint64, + ) uint64 { + return onMap(v) + } +} diff --git a/go/types/v1/fingerprint_test.go b/go/types/v1/fingerprint_test.go new file mode 100644 index 0000000..c322942 --- /dev/null +++ b/go/types/v1/fingerprint_test.go @@ -0,0 +1,497 @@ +package typesv1 + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestFingerprintingStabilityAndEquivalence(t *testing.T) { + // check that the fingerprint hashes don't change by mistake + // and that generating fingerprints via various methods + // yields the same hash + tests := []struct { + name string + inFlex Valuer + inNative *Val + want uint64 + }{ + { + name: "string", + inFlex: ValuerString("hello world"), + inNative: ValStr("hello world"), + want: 0xd51bc6e5e5e55060, + }, + { + name: "trace_id", + inFlex: ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + inNative: ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")}), + want: 0x7ba764bd63984a7c, + }, + { + name: "span_id", + inFlex: ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + inNative: ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")}), + want: 0x507d08e7656c0c59, + }, + { + name: "ulid", + inFlex: ValuerULID(42, 69), + inNative: ValULID(&ULID{High: 42, Low: 69}), + want: 0x8e46eb170aecbc91, + }, + { + name: "blob", + inFlex: ValuerBlob([]byte("lolololol")), + inNative: ValBlob([]byte("lolololol")), + want: 0xb583c15538d55d3d, + }, + { + name: "null", + inFlex: ValuerNull(), + inNative: ValNull(), + want: 0x9aaba41ffa2da101, + }, + { + name: "bool_true", + inFlex: ValuerBool(true), + inNative: ValBool(true), + want: 0x52fc4823a13c997a, + }, + { + name: "bool_false", + inFlex: ValuerBool(false), + inNative: ValBool(false), + want: 0xdd8f621dbf7f57f1, + }, + { + name: "i64", + inFlex: ValuerI64(666), + inNative: ValI64(666), + want: 0x646b7a027e623070, + }, + { + name: "f64", + inFlex: ValuerF64(123.456789), + inNative: ValF64(123.456789), + want: 0x2374c61b9f60cd7f, + }, + { + name: "hash64", + inFlex: ValuerHash64(0x2374c61b9f60cd7f), + inNative: ValHash64(0x2374c61b9f60cd7f), + want: 0xfeedafa8721cee71, + }, + { + name: "timestamp", + inFlex: ValuerTimestamp(1745971200, 50042), + inNative: ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042}), + want: 0xeee5ec8c1147bb7d, + }, + { + name: "duration_time", + inFlex: ValuerDuration(1745971200, 50042), + inNative: ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration()), + want: 0xd76a8183751862ad, + }, + { + name: "duration_pb", + inFlex: ValuerDuration(1745971200, 50042), + inNative: ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}), + want: 0xd76a8183751862ad, + }, + { + name: "array", + inFlex: ValuerArr(ValuerIteratorFromSlice([]Valuer{ + ValuerString("hello world"), + ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + ValuerULID(42, 69), + ValuerBlob([]byte("lolololol")), + ValuerNull(), + ValuerBool(true), + ValuerBool(false), + ValuerI64(666), + ValuerF64(123.456789), + ValuerHash64(0x2374c61b9f60cd7f), + ValuerTimestamp(1745971200, 50042), + ValuerDuration(1745971200, 50042), + ValuerDuration(1745971200, 50042), + ValuerObj(ValuerIteratorFromObjMap(map[string]Valuer{ + "valuer_string": ValuerString("hello world"), + "valuer_trace_id": ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + "valuer_span_id": ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + "valuer_ulid": ValuerULID(42, 69), + "valuer_blob": ValuerBlob([]byte("lolololol")), + "valuer_null": ValuerNull(), + "valuer_bool_true": ValuerBool(true), + "valuer_bool_false": ValuerBool(false), + "valuer_i64": ValuerI64(666), + "valuer_f64": ValuerF64(123.456789), + "valuer_hash64": ValuerHash64(0x2374c61b9f60cd7f), + "valuer_timestamp": ValuerTimestamp(1745971200, 50042), + "valuer_duration": ValuerDuration(1745971200, 50042), + "valuer_duration_pb": ValuerDuration(1745971200, 50042), + "valuer_arr": ValuerArr(ValuerIteratorFromSlice([]Valuer{ + ValuerString("hello world"), + ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + ValuerULID(42, 69), + ValuerBlob([]byte("lolololol")), + ValuerNull(), + ValuerBool(true), + ValuerBool(false), + ValuerI64(666), + ValuerF64(123.456789), + ValuerHash64(0x2374c61b9f60cd7f), + ValuerTimestamp(1745971200, 50042), + ValuerDuration(1745971200, 50042), + ValuerDuration(1745971200, 50042), + })), + })), + })), + inNative: ValArr( + ValStr("hello world"), + ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")}), + ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")}), + ValULID(&ULID{High: 42, Low: 69}), + ValBlob([]byte("lolololol")), + ValNull(), + ValBool(true), + ValBool(false), + ValI64(666), + ValF64(123.456789), + ValHash64(0x2374c61b9f60cd7f), + ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042}), + ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration()), + ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}), + ValObj( + KeyVal("valuer_string", ValStr("hello world")), + KeyVal("valuer_trace_id", ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")})), + KeyVal("valuer_span_id", ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")})), + KeyVal("valuer_ulid", ValULID(&ULID{High: 42, Low: 69})), + KeyVal("valuer_blob", ValBlob([]byte("lolololol"))), + KeyVal("valuer_null", ValNull()), + KeyVal("valuer_bool_true", ValBool(true)), + KeyVal("valuer_bool_false", ValBool(false)), + KeyVal("valuer_i64", ValI64(666)), + KeyVal("valuer_f64", ValF64(123.456789)), + KeyVal("valuer_hash64", ValHash64(0x2374c61b9f60cd7f)), + KeyVal("valuer_timestamp", ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042})), + KeyVal("valuer_duration", ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration())), + KeyVal("valuer_duration_pb", ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042})), + KeyVal("valuer_arr", ValArr( + ValStr("hello world"), + ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")}), + ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")}), + ValULID(&ULID{High: 42, Low: 69}), + ValBlob([]byte("lolololol")), + ValNull(), + ValBool(true), + ValBool(false), + ValI64(666), + ValF64(123.456789), + ValHash64(0x2374c61b9f60cd7f), + ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042}), + ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration()), + ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}), + )), + ), + ), + want: 0xd23056e5594b15c2, + }, + { + name: "obj", + inFlex: ValuerObj(ValuerIteratorFromObjMap(map[string]Valuer{ + "valuer_string": ValuerString("hello world"), + "valuer_trace_id": ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + "valuer_span_id": ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + "valuer_ulid": ValuerULID(42, 69), + "valuer_blob": ValuerBlob([]byte("lolololol")), + "valuer_null": ValuerNull(), + "valuer_bool_true": ValuerBool(true), + "valuer_bool_false": ValuerBool(false), + "valuer_i64": ValuerI64(666), + "valuer_f64": ValuerF64(123.456789), + "valuer_hash64": ValuerHash64(0x2374c61b9f60cd7f), + "valuer_timestamp": ValuerTimestamp(1745971200, 50042), + "valuer_duration": ValuerDuration(1745971200, 50042), + "valuer_duration_pb": ValuerDuration(1745971200, 50042), + "valuer_arr": ValuerArr(ValuerIteratorFromSlice([]Valuer{ + ValuerString("hello world"), + ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + ValuerULID(42, 69), + ValuerBlob([]byte("lolololol")), + ValuerNull(), + ValuerBool(true), + ValuerBool(false), + ValuerI64(666), + ValuerF64(123.456789), + ValuerHash64(0x2374c61b9f60cd7f), + ValuerTimestamp(1745971200, 50042), + ValuerDuration(1745971200, 50042), + ValuerDuration(1745971200, 50042), + })), + })), + inNative: ValObj( + KeyVal("valuer_string", ValStr("hello world")), + KeyVal("valuer_trace_id", ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")})), + KeyVal("valuer_span_id", ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")})), + KeyVal("valuer_ulid", ValULID(&ULID{High: 42, Low: 69})), + KeyVal("valuer_blob", ValBlob([]byte("lolololol"))), + KeyVal("valuer_null", ValNull()), + KeyVal("valuer_bool_true", ValBool(true)), + KeyVal("valuer_bool_false", ValBool(false)), + KeyVal("valuer_i64", ValI64(666)), + KeyVal("valuer_f64", ValF64(123.456789)), + KeyVal("valuer_hash64", ValHash64(0x2374c61b9f60cd7f)), + KeyVal("valuer_timestamp", ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042})), + KeyVal("valuer_duration", ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration())), + KeyVal("valuer_duration_pb", ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042})), + KeyVal("valuer_arr", ValArr( + ValStr("hello world"), + ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")}), + ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")}), + ValULID(&ULID{High: 42, Low: 69}), + ValBlob([]byte("lolololol")), + ValNull(), + ValBool(true), + ValBool(false), + ValI64(666), + ValF64(123.456789), + ValHash64(0x2374c61b9f60cd7f), + ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042}), + ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration()), + ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}), + )), + ), + want: 0x16e4a885e4242705, + }, + { + name: "map", + inFlex: ValuerMap(ValuerIteratorFromMap([]ValuerMapEntry{ + {ValuerString("valuer_string"), ValuerString("hello world")}, + {ValuerString("valuer_trace_id"), ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9"))}, + {ValuerString("valuer_span_id"), ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df"))}, + {ValuerString("valuer_ulid"), ValuerULID(42, 69)}, + {ValuerString("valuer_blob"), ValuerBlob([]byte("lolololol"))}, + {ValuerString("valuer_null"), ValuerNull()}, + {ValuerString("valuer_bool_true"), ValuerBool(true)}, + {ValuerString("valuer_bool_false"), ValuerBool(false)}, + {ValuerString("valuer_i64"), ValuerI64(666)}, + {ValuerString("valuer_f64"), ValuerF64(123.456789)}, + {ValuerString("valuer_hash64"), ValuerHash64(0x2374c61b9f60cd7f)}, + {ValuerString("valuer_timestamp"), ValuerTimestamp(1745971200, 50042)}, + {ValuerString("valuer_duration"), ValuerDuration(1745971200, 50042)}, + {ValuerString("valuer_duration_pb"), ValuerDuration(1745971200, 50042)}, + {ValuerString("valuer_arr"), ValuerArr(ValuerIteratorFromSlice([]Valuer{ + ValuerString("hello world"), + ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + ValuerULID(42, 69), + ValuerBlob([]byte("lolololol")), + ValuerNull(), + ValuerBool(true), + ValuerBool(false), + ValuerI64(666), + ValuerF64(123.456789), + ValuerHash64(0x2374c61b9f60cd7f), + ValuerTimestamp(1745971200, 50042), + ValuerDuration(1745971200, 50042), + ValuerDuration(1745971200, 50042), + }))}, + })), + inNative: ValMap(TypeStr(), TypeUnknown(), + &Map_Entry{Key: ValStr("valuer_string"), Value: ValStr("hello world")}, + &Map_Entry{Key: ValStr("valuer_trace_id"), Value: ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")})}, + &Map_Entry{Key: ValStr("valuer_span_id"), Value: ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")})}, + &Map_Entry{Key: ValStr("valuer_ulid"), Value: ValULID(&ULID{High: 42, Low: 69})}, + &Map_Entry{Key: ValStr("valuer_blob"), Value: ValBlob([]byte("lolololol"))}, + &Map_Entry{Key: ValStr("valuer_null"), Value: ValNull()}, + &Map_Entry{Key: ValStr("valuer_bool_true"), Value: ValBool(true)}, + &Map_Entry{Key: ValStr("valuer_bool_false"), Value: ValBool(false)}, + &Map_Entry{Key: ValStr("valuer_i64"), Value: ValI64(666)}, + &Map_Entry{Key: ValStr("valuer_f64"), Value: ValF64(123.456789)}, + &Map_Entry{Key: ValStr("valuer_hash64"), Value: ValHash64(0x2374c61b9f60cd7f)}, + &Map_Entry{Key: ValStr("valuer_timestamp"), Value: ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042})}, + &Map_Entry{Key: ValStr("valuer_duration"), Value: ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration())}, + &Map_Entry{Key: ValStr("valuer_duration_pb"), Value: ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042})}, + &Map_Entry{Key: ValStr("valuer_arr"), Value: ValArr( + ValStr("hello world"), + ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")}), + ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")}), + ValULID(&ULID{High: 42, Low: 69}), + ValBlob([]byte("lolololol")), + ValNull(), + ValBool(true), + ValBool(false), + ValI64(666), + ValF64(123.456789), + ValHash64(0x2374c61b9f60cd7f), + ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042}), + ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration()), + ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}), + )}, + ), + want: 0xf90280d704efd25, + }, + + // invariant checks + { + name: "obj order doesn't matter", + inFlex: ValuerObj(ValuerIteratorFromObjMap(map[string]Valuer{ + "valuer_trace_id": ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + "valuer_span_id": ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + "valuer_ulid": ValuerULID(42, 69), + "valuer_blob": ValuerBlob([]byte("lolololol")), + "valuer_string": ValuerString("hello world"), + "valuer_null": ValuerNull(), + "valuer_bool_true": ValuerBool(true), + "valuer_bool_false": ValuerBool(false), + "valuer_duration_pb": ValuerDuration(1745971200, 50042), + "valuer_i64": ValuerI64(666), + "valuer_f64": ValuerF64(123.456789), + "valuer_hash64": ValuerHash64(0x2374c61b9f60cd7f), + "valuer_timestamp": ValuerTimestamp(1745971200, 50042), + "valuer_duration": ValuerDuration(1745971200, 50042), + "valuer_arr": ValuerArr(ValuerIteratorFromSlice([]Valuer{ + ValuerString("hello world"), + ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + ValuerF64(123.456789), + ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + ValuerULID(42, 69), + ValuerBlob([]byte("lolololol")), + ValuerNull(), + ValuerBool(true), + ValuerBool(false), + ValuerHash64(0x2374c61b9f60cd7f), + ValuerTimestamp(1745971200, 50042), + ValuerDuration(1745971200, 50042), + ValuerI64(666), + ValuerDuration(1745971200, 50042), + })), + })), + inNative: ValObj( + KeyVal("valuer_string", ValStr("hello world")), + KeyVal("valuer_ulid", ValULID(&ULID{High: 42, Low: 69})), + KeyVal("valuer_null", ValNull()), + KeyVal("valuer_bool_true", ValBool(true)), + KeyVal("valuer_trace_id", ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")})), + KeyVal("valuer_span_id", ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")})), + KeyVal("valuer_f64", ValF64(123.456789)), + KeyVal("valuer_blob", ValBlob([]byte("lolololol"))), + KeyVal("valuer_hash64", ValHash64(0x2374c61b9f60cd7f)), + KeyVal("valuer_timestamp", ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042})), + KeyVal("valuer_duration", ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration())), + KeyVal("valuer_duration_pb", ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042})), + KeyVal("valuer_arr", ValArr( + ValStr("hello world"), + ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")}), + ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration()), + ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")}), + ValBlob([]byte("lolololol")), + ValNull(), + ValBool(true), + ValBool(false), + ValI64(666), + ValULID(&ULID{High: 42, Low: 69}), + ValF64(123.456789), + ValHash64(0x2374c61b9f60cd7f), + ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042}), + ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}), + )), + KeyVal("valuer_bool_false", ValBool(false)), + KeyVal("valuer_i64", ValI64(666)), + ), + want: 0x16e4a885e4242705, + }, + { + name: "map order doesn't matter", + inFlex: ValuerMap(ValuerIteratorFromMap([]ValuerMapEntry{ + {ValuerString("valuer_bool_false"), ValuerBool(false)}, + {ValuerString("valuer_i64"), ValuerI64(666)}, + {ValuerString("valuer_f64"), ValuerF64(123.456789)}, + {ValuerString("valuer_hash64"), ValuerHash64(0x2374c61b9f60cd7f)}, + {ValuerString("valuer_blob"), ValuerBlob([]byte("lolololol"))}, + {ValuerString("valuer_null"), ValuerNull()}, + {ValuerString("valuer_arr"), ValuerArr(ValuerIteratorFromSlice([]Valuer{ + ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")), + ValuerI64(666), + ValuerF64(123.456789), + ValuerHash64(0x2374c61b9f60cd7f), + ValuerTimestamp(1745971200, 50042), + ValuerDuration(1745971200, 50042), + ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df")), + ValuerULID(42, 69), + ValuerBlob([]byte("lolololol")), + ValuerNull(), + ValuerBool(true), + ValuerString("hello world"), + ValuerBool(false), + ValuerDuration(1745971200, 50042), + }))}, + {ValuerString("valuer_bool_true"), ValuerBool(true)}, + {ValuerString("valuer_timestamp"), ValuerTimestamp(1745971200, 50042)}, + {ValuerString("valuer_duration"), ValuerDuration(1745971200, 50042)}, + {ValuerString("valuer_duration_pb"), ValuerDuration(1745971200, 50042)}, + {ValuerString("valuer_string"), ValuerString("hello world")}, + {ValuerString("valuer_trace_id"), ValuerTraceID(hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9"))}, + {ValuerString("valuer_span_id"), ValuerSpanID(hexToBytes(t, "aa071ea68f7b58df"))}, + {ValuerString("valuer_ulid"), ValuerULID(42, 69)}, + })), + inNative: ValMap(TypeStr(), TypeUnknown(), + &Map_Entry{Key: ValStr("valuer_string"), Value: ValStr("hello world")}, + &Map_Entry{Key: ValStr("valuer_trace_id"), Value: ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")})}, + &Map_Entry{Key: ValStr("valuer_span_id"), Value: ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")})}, + &Map_Entry{Key: ValStr("valuer_ulid"), Value: ValULID(&ULID{High: 42, Low: 69})}, + &Map_Entry{Key: ValStr("valuer_blob"), Value: ValBlob([]byte("lolololol"))}, + &Map_Entry{Key: ValStr("valuer_null"), Value: ValNull()}, + &Map_Entry{Key: ValStr("valuer_bool_true"), Value: ValBool(true)}, + &Map_Entry{Key: ValStr("valuer_bool_false"), Value: ValBool(false)}, + &Map_Entry{Key: ValStr("valuer_i64"), Value: ValI64(666)}, + &Map_Entry{Key: ValStr("valuer_f64"), Value: ValF64(123.456789)}, + &Map_Entry{Key: ValStr("valuer_hash64"), Value: ValHash64(0x2374c61b9f60cd7f)}, + &Map_Entry{Key: ValStr("valuer_timestamp"), Value: ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042})}, + &Map_Entry{Key: ValStr("valuer_duration"), Value: ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration())}, + &Map_Entry{Key: ValStr("valuer_duration_pb"), Value: ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042})}, + &Map_Entry{Key: ValStr("valuer_arr"), Value: ValArr( + ValStr("hello world"), + ValTraceID(&TraceID{Raw: hexToBytes(t, "87951ca8e63350cd631e1d0e31b0dbd9")}), + ValSpanID(&SpanID{Raw: hexToBytes(t, "aa071ea68f7b58df")}), + ValULID(&ULID{High: 42, Low: 69}), + ValBlob([]byte("lolololol")), + ValNull(), + ValBool(true), + ValBool(false), + ValI64(666), + ValF64(123.456789), + ValHash64(0x2374c61b9f60cd7f), + ValTimestamp(×tamppb.Timestamp{Seconds: 1745971200, Nanos: 50042}), + ValDuration((&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}).AsDuration()), + ValDurationPB(&durationpb.Duration{Seconds: 1745971200, Nanos: 50042}), + )}, + ), + want: 0xf90280d704efd25, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotFlex := Hash64Any(tt.inFlex) + gotNative := Hash64Value(tt.inNative) + gotNativeToFlex := Hash64Any(ValuerVal(tt.inNative)) + require.Equal(t, tt.want, gotFlex, "flex hashing") + require.Equal(t, tt.want, gotNative, "native hashing") + require.Equal(t, tt.want, gotNativeToFlex, "native to flex hashing") + }) + } +} + +func hexToBytes(t testing.TB, val string) []byte { + out, err := hex.DecodeString(val) + require.NoError(t, err) + return out +} diff --git a/go/types/v1/otel_resource.go b/go/types/v1/otel_resource.go index 602b39c..3864645 100644 --- a/go/types/v1/otel_resource.go +++ b/go/types/v1/otel_resource.go @@ -3,10 +3,8 @@ package typesv1 import semconv "go.opentelemetry.io/otel/semconv/v1.34.0" func NewResource(schemaURL string, kvs []*KV) *Resource { - h64 := Hash64String(schemaURL) - h64 ^= Hash64KeyValues_orderDoesntMatter(kvs) return &Resource{ - ResourceHash_64: h64, + ResourceHash_64: Hash64Resource(schemaURL, KVsToValuer(kvs)), SchemaUrl: schemaURL, Attributes: kvs, } diff --git a/go/types/v1/otel_scope.go b/go/types/v1/otel_scope.go index 2be254f..d7ca690 100644 --- a/go/types/v1/otel_scope.go +++ b/go/types/v1/otel_scope.go @@ -1,12 +1,8 @@ package typesv1 func NewScope(schemaURL, name, version string, kvs []*KV) *Scope { - h64 := Hash64String(schemaURL) - h64 ^= Hash64String(name) - h64 ^= Hash64String(version) - h64 ^= Hash64KeyValues_orderDoesntMatter(kvs) return &Scope{ - ScopeHash_64: h64, + ScopeHash_64: Hash64Scope(schemaURL, name, version, KVsToValuer(kvs)), SchemaUrl: schemaURL, Name: name, Version: version,