@@ -10,34 +10,56 @@ import {
1010 type TenantRegistryRecord ,
1111} from "../dist/index.js" ;
1212
13- function recordFor ( name : string , state : TenantRegistryRecord [ "state" ] = "active" ) : TenantRegistryRecord {
14- return { tenant : { name } , product : "orb" , state, createdAt : "2026-01-01T00:00:00.000Z" , updatedAt : "2026-01-01T00:00:00.000Z" } ;
13+ function recordFor (
14+ name : string ,
15+ product : TenantRegistryRecord [ "product" ] = "orb" ,
16+ state : TenantRegistryRecord [ "state" ] = "active" ,
17+ ) : TenantRegistryRecord {
18+ return { tenant : { name } , product, state, createdAt : "2026-01-01T00:00:00.000Z" , updatedAt : "2026-01-01T00:00:00.000Z" } ;
1519}
1620
17- test ( "createFakeTenantRegistry: upsert/get/list round-trip, sorted by tenant name" , async ( ) => {
21+ test ( "createFakeTenantRegistry: upsert/get/list round-trip, sorted by tenant name then product " , async ( ) => {
1822 const registry = createFakeTenantRegistry ( ) ;
1923
2024 await registry . upsert ( recordFor ( "zebra" ) ) ;
2125 await registry . upsert ( recordFor ( "acme" ) ) ;
2226
23- assert . deepEqual ( await registry . get ( "acme" ) , recordFor ( "acme" ) ) ;
24- assert . equal ( await registry . get ( "ghost" ) , undefined ) ;
27+ assert . deepEqual ( await registry . get ( "acme" , "orb" ) , recordFor ( "acme" ) ) ;
28+ assert . equal ( await registry . get ( "ghost" , "orb" ) , undefined ) ;
2529 assert . deepEqual (
2630 ( await registry . list ( ) ) . map ( ( record ) => record . tenant . name ) ,
2731 [ "acme" , "zebra" ] ,
2832 ) ;
2933} ) ;
3034
31- test ( "createFakeTenantRegistry: upsert overwrites an existing record for the same tenant" , async ( ) => {
35+ test ( "createFakeTenantRegistry: upsert overwrites an existing record for the same product+ tenant" , async ( ) => {
3236 const registry = createFakeTenantRegistry ( ) ;
33- await registry . upsert ( recordFor ( "acme" , "active" ) ) ;
37+ await registry . upsert ( recordFor ( "acme" , "orb" , " active") ) ;
3438
35- await registry . upsert ( recordFor ( "acme" , "torn down" ) ) ;
39+ await registry . upsert ( recordFor ( "acme" , "orb" , " torn down") ) ;
3640
37- assert . equal ( ( await registry . get ( "acme" ) ) ?. state , "torn down" ) ;
41+ assert . equal ( ( await registry . get ( "acme" , "orb" ) ) ?. state , "torn down" ) ;
3842 assert . equal ( ( await registry . list ( ) ) . length , 1 ) ;
3943} ) ;
4044
45+ // Mirrors container-driver.test.ts's product-scoped instance key — same name across products must not share
46+ // one registry row on a single shared registry (production's HTTP composition shape; #8024).
47+ test ( "createFakeTenantRegistry: state is product-scoped (${product}:${name}), not just the tenant name" , async ( ) => {
48+ const registry = createFakeTenantRegistry ( ) ;
49+
50+ await registry . upsert ( recordFor ( "acme" , "orb" , "active" ) ) ;
51+ await registry . upsert ( recordFor ( "acme" , "ams" , "active" ) ) ;
52+
53+ assert . equal ( ( await registry . get ( "acme" , "orb" ) ) ?. product , "orb" ) ;
54+ assert . equal ( ( await registry . get ( "acme" , "ams" ) ) ?. product , "ams" ) ;
55+ assert . equal ( ( await registry . list ( ) ) . length , 2 ) ;
56+
57+ await registry . upsert ( recordFor ( "acme" , "orb" , "torn down" ) ) ;
58+
59+ assert . equal ( ( await registry . get ( "acme" , "orb" ) ) ?. state , "torn down" ) ;
60+ assert . equal ( ( await registry . get ( "acme" , "ams" ) ) ?. state , "active" ) ;
61+ } ) ;
62+
4163function fakeKv ( initial : Record < string , string > = { } ) : KvNamespaceLike & { store : Map < string , string > } {
4264 const store = new Map ( Object . entries ( initial ) ) ;
4365 return {
@@ -60,33 +82,33 @@ function fakeKv(initial: Record<string, string> = {}): KvNamespaceLike & { store
6082 } ;
6183}
6284
63- test ( "createKvTenantRegistry: upsert writes a JSON-encoded value under the tenant: prefix " , async ( ) => {
85+ test ( "createKvTenantRegistry: upsert writes a JSON-encoded value under tenant:${product}:${name} " , async ( ) => {
6486 const kv = fakeKv ( ) ;
6587 const registry = createKvTenantRegistry ( kv ) ;
6688
6789 await registry . upsert ( recordFor ( "acme" ) ) ;
6890
69- assert . equal ( kv . store . get ( "tenant:acme" ) , JSON . stringify ( recordFor ( "acme" ) ) ) ;
91+ assert . equal ( kv . store . get ( "tenant:orb: acme" ) , JSON . stringify ( recordFor ( "acme" ) ) ) ;
7092} ) ;
7193
7294test ( "createKvTenantRegistry: get returns undefined for a key that was never written" , async ( ) => {
7395 const registry = createKvTenantRegistry ( fakeKv ( ) ) ;
7496
75- assert . equal ( await registry . get ( "ghost" ) , undefined ) ;
97+ assert . equal ( await registry . get ( "ghost" , "orb" ) , undefined ) ;
7698} ) ;
7799
78100test ( "createKvTenantRegistry: get parses a previously written record back" , async ( ) => {
79- const kv = fakeKv ( { "tenant:acme" : JSON . stringify ( recordFor ( "acme" ) ) } ) ;
101+ const kv = fakeKv ( { "tenant:orb: acme" : JSON . stringify ( recordFor ( "acme" ) ) } ) ;
80102 const registry = createKvTenantRegistry ( kv ) ;
81103
82- assert . deepEqual ( await registry . get ( "acme" ) , recordFor ( "acme" ) ) ;
104+ assert . deepEqual ( await registry . get ( "acme" , "orb" ) , recordFor ( "acme" ) ) ;
83105} ) ;
84106
85107test ( "createKvTenantRegistry: list pages through multiple KV list() pages and returns every record, sorted" , async ( ) => {
86108 const kv = fakeKv ( {
87- "tenant:charlie" : JSON . stringify ( recordFor ( "charlie" ) ) ,
88- "tenant:alpha" : JSON . stringify ( recordFor ( "alpha" ) ) ,
89- "tenant:bravo" : JSON . stringify ( recordFor ( "bravo" ) ) ,
109+ "tenant:orb: charlie" : JSON . stringify ( recordFor ( "charlie" ) ) ,
110+ "tenant:orb: alpha" : JSON . stringify ( recordFor ( "alpha" ) ) ,
111+ "tenant:orb: bravo" : JSON . stringify ( recordFor ( "bravo" ) ) ,
90112 } ) ;
91113 const registry = createKvTenantRegistry ( kv ) ;
92114
@@ -98,12 +120,30 @@ test("createKvTenantRegistry: list pages through multiple KV list() pages and re
98120 ) ;
99121} ) ;
100122
123+ test ( "createKvTenantRegistry: list returns both products when the same tenant name is registered twice" , async ( ) => {
124+ const kv = fakeKv ( {
125+ "tenant:orb:acme" : JSON . stringify ( recordFor ( "acme" , "orb" ) ) ,
126+ "tenant:ams:acme" : JSON . stringify ( recordFor ( "acme" , "ams" ) ) ,
127+ } ) ;
128+ const registry = createKvTenantRegistry ( kv ) ;
129+
130+ const records = await registry . list ( ) ;
131+
132+ assert . deepEqual (
133+ records . map ( ( record ) => [ record . tenant . name , record . product ] ) ,
134+ [
135+ [ "acme" , "ams" ] ,
136+ [ "acme" , "orb" ] ,
137+ ] ,
138+ ) ;
139+ } ) ;
140+
101141test ( "createKvTenantRegistry: list tolerates a key disappearing between the list() page and the get() read" , async ( ) => {
102- const kv = fakeKv ( { "tenant:acme" : JSON . stringify ( recordFor ( "acme" ) ) } ) ;
142+ const kv = fakeKv ( { "tenant:orb: acme" : JSON . stringify ( recordFor ( "acme" ) ) } ) ;
103143 const originalGet = kv . get . bind ( kv ) ;
104144 kv . get = async ( key : string ) => {
105145 // Simulate a concurrent delete: the key was listed, but its value is gone by the time we read it.
106- if ( key === "tenant:acme" ) return null ;
146+ if ( key === "tenant:orb: acme" ) return null ;
107147 return originalGet ( key ) ;
108148 } ;
109149 const registry = createKvTenantRegistry ( kv ) ;
0 commit comments