From 22e2c63eafa32ac15631dac497f5021e4be396a8 Mon Sep 17 00:00:00 2001 From: Samuel M Smith Date: Sun, 17 Mar 2024 10:50:22 -0700 Subject: [PATCH 1/3] added more support for versioning of Serder. Need to fix some tests --- src/keri/core/serdering.py | 34 ++++++--- src/keri/kering.py | 2 +- tests/core/test_eventing.py | 6 +- tests/core/test_serdering.py | 139 +++++++++++++++++++++++++++++++---- tests/test_kering.py | 6 +- 5 files changed, 156 insertions(+), 31 deletions(-) diff --git a/src/keri/core/serdering.py b/src/keri/core/serdering.py index 046b26559..baf494cbc 100644 --- a/src/keri/core/serdering.py +++ b/src/keri/core/serdering.py @@ -22,8 +22,10 @@ ShortageError, VersionError, ProtocolError, KindError, DeserializeError, FieldError, SerializeError) from ..kering import (Versionage, Version, Vrsn_1_0, Vrsn_2_0, - VERRAWSIZE, VERFMT, MAXVERFULLSPAN, - SMELLSIZE, Smellage, smell) + VERRAWSIZE, VERFMT, + MAXVERFULLSPAN, VER1FULLSPAN, VER2FULLSPAN) +from ..kering import SMELLSIZE, Smellage, smell + from ..kering import Protos, Serials, Rever, versify, deversify, Ilks from ..core import coring from .coring import MtrDex, DigDex, PreDex, Saids, Digestage @@ -178,6 +180,7 @@ class Serder: Class Attributes: Dummy (str): dummy character for computing SAIDs + Spans (dict): version string spans keyed by version Digests (dict): map of digestive codes. Should be same set of codes as in coring.DigestCodex coring.DigDex so that .digestive property works. Use unit tests to ensure codex sets match @@ -244,6 +247,9 @@ class Serder: """ Dummy = "#" # dummy spaceholder char for SAID. Must not be a valid Base64 char + # Spans dict keyed by version (Versionage instance) of version string span (size) + Spans = {Vrsn_1_0: VER1FULLSPAN, Vrsn_2_0: VER2FULLSPAN} + # should be same set of codes as in coring.DigestCodex coring.DigDex so # .digestive property works. Use unit tests to ensure codex sets match Digests = { @@ -539,13 +545,13 @@ def __init__(self, *, raw=b'', sad=None, strip=False, version=Version, except Exception: self._said = None # no saidive field - if verify: # verify the said(s) provided in sad - try: - self._verify() # raises exception when not verify - except Exception as ex: - logger.error("Invalid sad for Serder %s\n%s", - self.pretty(), ex.args[0]) - raise ValidationError(f"Invalid sad for Serder =" + if verify: # verify the said(s) provided in sad + try: + self._verify() # raises exception when not verify + except Exception as ex: + logger.error("Invalid sad for Serder %s\n%s", + self.pretty(), ex.args[0]) + raise ValidationError(f"Invalid sad for Serder =" f"{self._sad}.") from ex else: @@ -771,6 +777,10 @@ def makify(self, sad, *, version=None, value = copy.copy(value) # copy iterable defaults sad[label] = value + # Need to insert required fields in proper place because passed in sad + # may have missing require fields that appear before provided ones so + # can't simply append + if 't' in sad: # when packet type field then force ilk sad['t'] = ilk # assign ilk @@ -823,9 +833,11 @@ def makify(self, sad, *, version=None, raise SerializeError(f"Missing requires version string field 'v'" f" in sad = {sad}.") - sad['v'] = self.Dummy * MAXVERFULLSPAN # ensure size of vs + # this size of sad needs to be computed based on actual version string span + # since not same for all versions + sad['v'] = self.Dummy * self.Spans[vrsn] # ensure span of vs is dummied MAXVERFULLSPAN - raw = self.dumps(sad, kind) # get size of fully dummied sad + raw = self.dumps(sad, kind) # get size of sad with fully dummied vs and saids size = len(raw) # generate new version string with correct size diff --git a/src/keri/kering.py b/src/keri/kering.py index 099af0b1b..5222c22d8 100644 --- a/src/keri/kering.py +++ b/src/keri/kering.py @@ -46,7 +46,7 @@ VEREX = VEREX2 + b'|' + VEREX1 -MAXVERFULLSPAN = max(VER1FULLSPAN, VER2FULLSPAN) # number of characters in full version string +MAXVERFULLSPAN = max(VER1FULLSPAN, VER2FULLSPAN) # max number of characters in full version string Rever = re.compile(VEREX) # compile is faster diff --git a/tests/core/test_eventing.py b/tests/core/test_eventing.py index 9bf8d2ad4..69850c833 100644 --- a/tests/core/test_eventing.py +++ b/tests/core/test_eventing.py @@ -2090,7 +2090,8 @@ def test_kever(mockHelpingNowUTC): # make with defaults with non-digestive prefix serder = serdering.SerderKERI(makify=True, ilk=kering.Ilks.icp, - saids = {'i': coring.PreDex.Ed25519}) + saids = {'i': coring.PreDex.Ed25519}, + verify=False) sad = serder.sad sad['i'] = skp0.verfer.qb64 # non-digestive aid @@ -2233,7 +2234,8 @@ def test_kever(mockHelpingNowUTC): # make with defaults with non-transferable prefix serder = serdering.SerderKERI(makify=True, ilk=kering.Ilks.icp, - saids = {'i': coring.PreDex.Ed25519N}) + saids = {'i': coring.PreDex.Ed25519N}, + verify=False) sad = serder.sad sad['i'] = skp0.verfer.qb64 # non-digestive aid diff --git a/tests/core/test_serdering.py b/tests/core/test_serdering.py index 59081a094..03678f83b 100644 --- a/tests/core/test_serdering.py +++ b/tests/core/test_serdering.py @@ -14,7 +14,9 @@ from ordered_set import OrderedSet as oset from keri import kering -from keri.kering import Versionage, Version +from keri.kering import (Versionage, Version, Vrsn_1_0, Vrsn_2_0, + VERRAWSIZE, VERFMT, + MAXVERFULLSPAN, VER1FULLSPAN, VER2FULLSPAN) from keri.core import coring @@ -41,6 +43,17 @@ def test_fielddom(): """End Test""" +def test_spans(): + """ + Test Spans dict of version string sizes by version + """ + assert Serder.Spans + assert isinstance(Serder.Spans, dict) + + assert Serder.Spans[kering.Vrsn_1_0] == kering.VER1FULLSPAN == 17 + assert Serder.Spans[kering.Vrsn_2_0] == kering.VER2FULLSPAN == 16 + + """End Test""" def test_serder(): @@ -437,7 +450,8 @@ def test_serder(): # Test with non-digestive code for 'i' saidive field no sad serder = Serder(makify=True, ilk=kering.Ilks.icp, - saids = {'i': coring.PreDex.Ed25519}) + saids = {'i': coring.PreDex.Ed25519}, + verify=False) assert serder.sad == {'v': 'KERI10JSON0000a3_', 't': 'icp', @@ -881,7 +895,8 @@ def test_serderkeri_icp(): # Test with non-digestive code for 'i' saidive field no sad serder = SerderKERI(makify=True, ilk=kering.Ilks.icp, - saids = {'i': coring.PreDex.Ed25519}) + saids = {'i': coring.PreDex.Ed25519}, + verify=False) assert serder.sad == {'v': 'KERI10JSON0000a3_', 't': 'icp', @@ -1018,7 +1033,7 @@ def test_serderkeri_rot(): """Test SerderKERI rot msg""" # Test KERI JSON with makify defaults for self bootstrap with ilk rot - serder = SerderKERI(makify=True, ilk=kering.Ilks.rot) # make with defaults + serder = SerderKERI(makify=True, ilk=kering.Ilks.rot, verify=False) # make with defaults assert serder.sad == {'v': 'KERI10JSON0000ac_', 't': 'rot', 'd': 'EMgauZPVfh6807jO9QO8A4Iauq1xhYTZnKX2doVd_UDl', @@ -1135,7 +1150,7 @@ def test_serderkeri_ixn(): """Test SerderKERI ixn msg""" # Test KERI JSON with makify defaults for self bootstrap with ilk ixn - serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn) # make with defaults + serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn, verify=False) # make with defaults assert serder.sad == {'v': 'KERI10JSON000073_', 't': 'ixn', 'd': 'ELI1jUxlJky6RvRieoO20H7_YikKnQMthnWM38etba3r', @@ -1245,7 +1260,7 @@ def test_serderkeri_dip(): """Test SerderKERI dip msg""" # Test KERI JSON with makify defaults for self bootstrap with ilk dip - serder = SerderKERI(makify=True, ilk=kering.Ilks.dip) # make with defaults + serder = SerderKERI(makify=True, ilk=kering.Ilks.dip, verify=False) # make with defaults assert serder.sad == {'v': 'KERI10JSON0000d7_', 't': 'dip', 'd': 'EPyzEgwg6ls8iY4jViniM15rAFWaaVbsZ4eP2a9ZcKfC', @@ -1378,7 +1393,8 @@ def test_serderkeri_dip(): # Test with non-digestive code for 'i' saidive field no sad serder = SerderKERI(makify=True, ilk=kering.Ilks.dip, - saids = {'i': coring.PreDex.Ed25519}) + saids = {'i': coring.PreDex.Ed25519}, + verify=False) assert serder.sad == {'v': 'KERI10JSON0000ab_', 't': 'dip', @@ -1412,7 +1428,7 @@ def test_serderkeri_dip(): sad['i'] = pre sad['di'] = delpre - serder = SerderKERI(sad=sad, makify=True) + serder = SerderKERI(sad=sad, makify=True, verify=False) assert not serder.verify() pre = 'EF78YGUYCWXptoVVel1TN1F9-KShPHAtEqvf-TEiGvv9' @@ -1547,7 +1563,7 @@ def test_serderkeri_dip(): def test_serderkeri_drt(): """Test SerderKERI drt msg""" # Test KERI JSON with makify defaults for self bootstrap with ilk drt - serder = SerderKERI(makify=True, ilk=kering.Ilks.drt) # make with defaults + serder = SerderKERI(makify=True, ilk=kering.Ilks.drt, verify=False) # make with defaults assert serder.sad == {'v': 'KERI10JSON0000ac_', 't': 'drt', 'd': 'EMiEhgKRsD559TX6b03AT5P2GfKPPqoNk5COHZxU2TkR', @@ -1578,7 +1594,7 @@ def test_serderkeri_drt(): pre = 'DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx' sad['i'] = pre - serder = SerderKERI(sad=sad, makify=True) + serder = SerderKERI(sad=sad, makify=True, verify=False) assert not serder.verify() # because pre is not digest and delpre is empty sad = serder.sad @@ -1671,7 +1687,7 @@ def test_serderkeri_rct(): """Test SerderKERI rct msg""" # Test KERI JSON with makify defaults for self bootstrap with ilk ixn - serder = SerderKERI(makify=True, ilk=kering.Ilks.rct) # make with defaults + serder = SerderKERI(makify=True, ilk=kering.Ilks.rct, verify=False) # make with defaults assert serder.sad == {'v': 'KERI10JSON000039_', 't': 'rct', 'd': '', 'i': '', 's': '0'} assert serder.raw == b'{"v":"KERI10JSON000039_","t":"rct","d":"","i":"","s":"0"}' @@ -2392,7 +2408,7 @@ def test_serderacdc(): with pytest.raises(ValueError): serder = SerderACDC() - serder = SerderACDC(makify=True, proto=kering.Protos.acdc) # make defaults for ACDC + serder = SerderACDC(makify=True, proto=kering.Protos.acdc, verify=False) # make defaults for ACDC assert serder.sad == {'v': 'ACDC10JSON00005a_', 'd': 'EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pT', 'i': '', @@ -2456,12 +2472,101 @@ def test_serderacdc(): """End Test""" +def test_serder_v2(): + """ + Test Serder with version 2.00 of protocols + """ + + + assert Serder.Fields[kering.Protos.acdc][kering.Vrsn_2_0][None].saids == {'d': 'E'} + assert (Serder.Fields[kering.Protos.acdc][kering.Vrsn_2_0][None].alls == + {'v': '', 'd': '', 'u': '', 'i': '', 'rd': '', 's': '', 'a': '', 'A': '', 'e': '', 'r': ''}) + assert (Serder.Fields[kering.Protos.acdc][kering.Vrsn_2_0][None].opts == + {'u': '', 'rd': '', 'a': '', 'A': '', 'e': '', 'r': ''}) + assert (Serder.Fields[kering.Protos.acdc][kering.Vrsn_2_0][None].alts == + {'a': 'A', 'A': 'a'}) + assert Serder.Fields[kering.Protos.acdc][kering.Vrsn_2_0][None].strict + + + + with pytest.raises(ValueError): + serder = Serder(version=kering.Vrsn_2_0) + + #Test Serder bare makify bootstrap for ACDC JSON + serder = Serder(makify=True, + proto=kering.Protos.acdc, + vrsn=kering.Vrsn_2_0, + version=kering.Vrsn_2_0) # make defaults for ACDC + assert serder.sad == {'v': 'ACDCCAAJSONAABZ.', + 'd': 'EN-uBXL6rsJpJvDSsyOAnttQiI9gka4qLbe3MlIoYwYy', + 'i': '', + 's': ''} + assert serder.raw == (b'{"v":"ACDCCAAJSONAABZ.","d":"EN-uBXL6rsJpJvDSsyOAnttQiI9gka4qLbe3MlIoYwYy","' + b'i":"","s":""}') + assert serder.verify() + sad = serder.sad + raw = serder.raw + said = serder.said + size = serder.size + + serder = Serder(sad=sad, version=kering.Vrsn_2_0) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_2_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + assert serder.compare(said=said.encode("utf-8")) + assert not serder.compare(said='EMk7BvrqO_2sYjpI_-BmSELOFNie-muw4XTi3iYCz6pE') + assert serder.pretty() == ('{\n' + ' "v": "ACDCCAAJSONAABZ.",\n' + ' "d": "EN-uBXL6rsJpJvDSsyOAnttQiI9gka4qLbe3MlIoYwYy",\n' + ' "i": "",\n' + ' "s": ""\n' + '}') + + serder = Serder(raw=raw, version=kering.Vrsn_2_0) + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_2_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + assert serder.compare(said=said.encode("utf-8")) + + serder = Serder(sad=sad, makify=True, version=kering.Vrsn_2_0) # test makify with sad + assert serder.raw == raw + assert isinstance(serder.raw, bytes) + assert serder.sad == sad + assert serder.proto == kering.Protos.acdc + assert serder.vrsn == kering.Vrsn_2_0 + assert serder.size == size + assert serder.kind == kering.Serials.json + assert serder.said == said + assert serder.saidb == said.encode("utf-8") + assert serder.ilk == None + assert serder.compare(said=said) + + + """End Test""" + + def test_serdery(): """Test Serdery""" #Create incoming message stream for Serdery to reap - serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn) # make with defaults + serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn, verify=False) # make with defaults sad = serder.sad pre = "EDGnGYIa5obfFUhxcAuUmM4fJyeRYj2ti3KGf87Bc70J" sad['i'] = pre @@ -2472,7 +2577,7 @@ def test_serdery(): ims = bytearray(serderKeri.raw) - serder = SerderACDC(makify=True, proto=kering.Protos.acdc) # make defaults for ACDC + serder = SerderACDC(makify=True, proto=kering.Protos.acdc, verify=False) # make defaults for ACDC sad = serder.sad isr = 'EO8CE5RH1X8QJwHHhPkj_S6LJQDRNOiGohW327FMA6D2' sad['i'] = isr @@ -2514,7 +2619,7 @@ def test_serdery_noversion(): """Test Serdery unsupported version""" #Create incoming message stream for Serdery to reap - serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn) # make with defaults + serder = SerderKERI(makify=True, ilk=kering.Ilks.ixn, verify=False) # make with defaults sad = serder.sad pre = "EDGnGYIa5obfFUhxcAuUmM4fJyeRYj2ti3KGf87Bc70J" sad['i'] = pre @@ -2525,7 +2630,7 @@ def test_serdery_noversion(): ims = bytearray(serderKeri.raw) - serder = SerderACDC(makify=True, proto=kering.Protos.acdc) # make defaults for ACDC + serder = SerderACDC(makify=True, proto=kering.Protos.acdc, verify=False) # make defaults for ACDC sad = serder.sad isr = 'EO8CE5RH1X8QJwHHhPkj_S6LJQDRNOiGohW327FMA6D2' sad['i'] = isr @@ -2566,6 +2671,7 @@ def test_serdery_noversion(): if __name__ == "__main__": test_fielddom() + test_spans() test_serder() test_serderkeri() test_serderkeri_icp() @@ -2581,5 +2687,6 @@ def test_serdery_noversion(): test_serderkeri_exn() test_serderkeri_vcp() test_serderacdc() + test_serder_v2() test_serdery() test_serdery_noversion() diff --git a/tests/test_kering.py b/tests/test_kering.py index d4bf2a6ea..96b47de7e 100644 --- a/tests/test_kering.py +++ b/tests/test_kering.py @@ -19,6 +19,7 @@ versify, deversify, Rever) from keri.kering import (VER1FULLSPAN, VER1TERM, VEREX1, VER2FULLSPAN, VER2TERM, VEREX2, VEREX) + from keri.kering import VersionError, ProtocolError, KindError from keri.help.helping import (intToB64, intToB64b, b64ToInt, B64_CHARS, codeB64ToB2, codeB2ToB64, Reb64, nabSextets) @@ -43,6 +44,8 @@ def test_protos(): """End Test""" + + def test_version_regex(): """ Test version string regexing @@ -480,9 +483,10 @@ def test_versify_v2(): with pytest.raises(KindError): smellage = deversify(vs) + """End Test""" + - """End Test""" def test_ilks(): From 109d72bdea9546f5f06fbee58b667e8929e905b5 Mon Sep 17 00:00:00 2001 From: Samuel M Smith Date: Sun, 17 Mar 2024 16:22:48 -0700 Subject: [PATCH 2/3] fixed tests now that Makify allows verify fix bug --- tests/core/test_eventing.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/core/test_eventing.py b/tests/core/test_eventing.py index 69850c833..785b83fca 100644 --- a/tests/core/test_eventing.py +++ b/tests/core/test_eventing.py @@ -2246,7 +2246,7 @@ def test_kever(mockHelpingNowUTC): sad['n'] = nxt sad['bt'] = "{:x}".format(toad) - serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + serder = serdering.SerderKERI(makify=True, verify=False, sad=sad) assert serder.said == 'EFsuiA86Q5gGuVOO3tou8KSU6LORSExIUxzWNrlnW7WP' assert serder.pre == skp0.verfer.qb64 aid0 = serder.pre @@ -2272,7 +2272,8 @@ def test_kever(mockHelpingNowUTC): # make with defaults with non-transferable prefix serder = serdering.SerderKERI(makify=True, ilk=kering.Ilks.icp, - saids = {'i': coring.PreDex.Ed25519N}) + saids = {'i': coring.PreDex.Ed25519N}, + verify=False) sad = serder.sad sad['i'] = skp0.verfer.qb64 # non-digestive aid @@ -2280,7 +2281,7 @@ def test_kever(mockHelpingNowUTC): sad['kt'] = "{:x}".format(sith) # hex string sad['k'] = keys sad['nt'] = 0 - sad['n'] = nxt + sad['n'] = nxt # empty nxt sad['bt'] = "{:x}".format(toad) serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) @@ -2329,7 +2330,8 @@ def test_kever(mockHelpingNowUTC): # make with defaults with non-transferable prefix serder = serdering.SerderKERI(makify=True, ilk=kering.Ilks.icp, - saids = {'i': coring.PreDex.Ed25519N}) + saids = {'i': coring.PreDex.Ed25519N}, + verify=False) sad = serder.sad sad['i'] = skp0.verfer.qb64 # non-digestive aid @@ -2341,7 +2343,7 @@ def test_kever(mockHelpingNowUTC): sad['bt'] = "{:x}".format(toad) sad['b'] = baks - serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + serder = serdering.SerderKERI(makify=True, verify=False, sad=sad) assert serder.said == 'EKcREpfNupJ8oOqdnqDIyJVr1-GgIMBrVOtBUR9Gm6lO' assert serder.pre == skp0.verfer.qb64 @@ -2364,7 +2366,7 @@ def test_kever(mockHelpingNowUTC): sad =serder.sad # makes copy sad['bt'] = "{:x}".format(toad) - serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + serder = serdering.SerderKERI(makify=True, verify=False, sad=sad) assert serder.said == 'EBKhptvqccp0KNBaS45bNPdTE4m19U1IvweHJW2PIEDI' assert serder.pre == skp0.verfer.qb64 @@ -2393,7 +2395,7 @@ def test_kever(mockHelpingNowUTC): sad['b'] = baks sad['a'] = a - serder = serdering.SerderKERI(makify=True, verify=True, sad=sad) + serder = serdering.SerderKERI(makify=True, verify=False, sad=sad) assert serder.said == 'EEu-cdj_9b_66XRJ5UuhgEvJxAPpn4RjyaHvRgDU3iyA' assert serder.pre == skp0.verfer.qb64 From 8521368763e353bcb6e94ac80f8f08e7591cc852 Mon Sep 17 00:00:00 2001 From: Samuel M Smith Date: Sun, 17 Mar 2024 16:37:42 -0700 Subject: [PATCH 3/3] minor refactor in how checks version compatibility with operating version --- src/keri/core/serdering.py | 9 ++++++--- src/keri/kering.py | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/keri/core/serdering.py b/src/keri/core/serdering.py index baf494cbc..71526a94f 100644 --- a/src/keri/core/serdering.py +++ b/src/keri/core/serdering.py @@ -744,9 +744,12 @@ def makify(self, sad, *, version=None, raise SerializeError(f"Expected protocol = {self.Protocol}, got " f"{proto} instead.") - if version is not None and vrsn != version: - raise SerializeError(f"Expected version = {version}, got " - f"{vrsn.major}.{vrsn.minor}.") + if version is not None: # compatible version with vrsn + if (vrsn.major > version.major or + (vrsn.major == version.major and vrsn.minor > version.minor)): + raise SerializeError(f"Incompatible {version=}, with " + f"{vrsn=}.") + if kind not in Serials: raise SerializeError(f"Invalid serialization kind = {kind}") diff --git a/src/keri/kering.py b/src/keri/kering.py index 5222c22d8..02a69b079 100644 --- a/src/keri/kering.py +++ b/src/keri/kering.py @@ -114,9 +114,12 @@ def rematch(match, *, version=None): vrsn = Versionage(major=int(major, 16), minor=int(minor, 16)) if vrsn.major > 1: # version1 vs but major > 1 raise VersionError(f"Incompatible {vrsn=} with version string.") - if version is not None and vrsn != version: - raise VersionError(f"Expected {version=}, got " + if version is not None: # compatible version with vrsn + if (vrsn.major > version.major or + (vrsn.major == version.major and vrsn.minor > version.minor)): + raise VersionError(f"Incompatible {version=}, with " f"{vrsn=}.") + kind = kind.decode("utf-8") if kind not in Serials: raise KindError(f"Invalid serialization kind = {kind}.")