@@ -27,10 +27,12 @@ type Services = {
2727const sampleDescription = (
2828 name = 'Signer' ,
2929 contactUrl = 'ocap:abc@peer' ,
30+ providerTag = name . toLowerCase ( ) ,
3031) : ServiceDescription => ( {
3132 apiSpec : { properties : { } } ,
3233 description : `A service called ${ name } ` ,
3334 contact : [ { contactType : 'public' , contactUrl } ] ,
35+ providerTag,
3436} ) ;
3537
3638/**
@@ -420,6 +422,7 @@ describe('matcher vat', () => {
420422 apiSpec : { properties : { } } ,
421423 description : 'no contact' ,
422424 contact : [ ] ,
425+ providerTag : 'lonely' ,
423426 } ;
424427
425428 await expect (
@@ -428,6 +431,95 @@ describe('matcher vat', () => {
428431 } ) ;
429432 } ) ;
430433
434+ describe ( 'same-peer same-tag dedup' , ( ) => {
435+ it ( 'evicts the previous registration when (peer, tag) matches' , async ( ) => {
436+ await root . bootstrap ( { } , mocks . services ) ;
437+ const publicFacet = root . getPublicFacet ( ) ;
438+
439+ // First registration: peerA + tag=signer
440+ const description1 = sampleDescription (
441+ 'Signer' ,
442+ 'ocap:key1@peerA' ,
443+ 'signer' ,
444+ ) ;
445+ const contactA = makeMockContact ( {
446+ description : description1 ,
447+ expectedToken : 'tok-A' ,
448+ } ) ;
449+ mocks . redeem . mockResolvedValueOnce ( contactA . contact ) ;
450+ await publicFacet . registerService ( description1 , 'tok-A' ) ;
451+ expect ( root . listAll ( ) ) . toHaveLength ( 1 ) ;
452+
453+ // Second registration: same peerA + same tag=signer (simulates the
454+ // same logical provider re-registering after a kernel restart with a
455+ // fresh contact endpoint).
456+ const description2 = sampleDescription (
457+ 'Signer' ,
458+ 'ocap:key2@peerA' ,
459+ 'signer' ,
460+ ) ;
461+ const contactB = makeMockContact ( {
462+ description : description2 ,
463+ expectedToken : 'tok-B' ,
464+ } ) ;
465+ mocks . redeem . mockResolvedValueOnce ( contactB . contact ) ;
466+ await publicFacet . registerService ( description2 , 'tok-B' ) ;
467+
468+ // Only the most recent (peerA, signer) registration survives.
469+ const all = root . listAll ( ) ;
470+ expect ( all ) . toHaveLength ( 1 ) ;
471+ expect ( all [ 0 ] ?. description . contact [ 0 ] ?. contactUrl ) . toBe (
472+ 'ocap:key2@peerA' ,
473+ ) ;
474+ } ) ;
475+
476+ it ( 'keeps both when same peer registers different tags' , async ( ) => {
477+ await root . bootstrap ( { } , mocks . services ) ;
478+ const publicFacet = root . getPublicFacet ( ) ;
479+
480+ const echo = sampleDescription ( 'Echo' , 'ocap:k1@peerA' , 'echo' ) ;
481+ const echoContact = makeMockContact ( {
482+ description : echo ,
483+ expectedToken : 'tok-A' ,
484+ } ) ;
485+ mocks . redeem . mockResolvedValueOnce ( echoContact . contact ) ;
486+ await publicFacet . registerService ( echo , 'tok-A' ) ;
487+
488+ const random = sampleDescription ( 'Random' , 'ocap:k2@peerA' , 'random' ) ;
489+ const randomContact = makeMockContact ( {
490+ description : random ,
491+ expectedToken : 'tok-B' ,
492+ } ) ;
493+ mocks . redeem . mockResolvedValueOnce ( randomContact . contact ) ;
494+ await publicFacet . registerService ( random , 'tok-B' ) ;
495+
496+ expect ( root . listAll ( ) ) . toHaveLength ( 2 ) ;
497+ } ) ;
498+
499+ it ( 'keeps both when different peers share a tag' , async ( ) => {
500+ await root . bootstrap ( { } , mocks . services ) ;
501+ const publicFacet = root . getPublicFacet ( ) ;
502+
503+ const fromA = sampleDescription ( 'Signer' , 'ocap:k1@peerA' , 'signer' ) ;
504+ const contactA = makeMockContact ( {
505+ description : fromA ,
506+ expectedToken : 'tok-A' ,
507+ } ) ;
508+ mocks . redeem . mockResolvedValueOnce ( contactA . contact ) ;
509+ await publicFacet . registerService ( fromA , 'tok-A' ) ;
510+
511+ const fromB = sampleDescription ( 'Signer' , 'ocap:k2@peerB' , 'signer' ) ;
512+ const contactB = makeMockContact ( {
513+ description : fromB ,
514+ expectedToken : 'tok-B' ,
515+ } ) ;
516+ mocks . redeem . mockResolvedValueOnce ( contactB . contact ) ;
517+ await publicFacet . registerService ( fromB , 'tok-B' ) ;
518+
519+ expect ( root . listAll ( ) ) . toHaveLength ( 2 ) ;
520+ } ) ;
521+ } ) ;
522+
431523 describe ( 'findServices' , ( ) => {
432524 it ( 'asks the bridge and returns whatever services it cites' , async ( ) => {
433525 const llm = makeMockLlm ( {
0 commit comments