diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0aef82debb458e44c3b9bfa1dceb4d2f79548bee..521e9b519646214a6154fc49f8195c60ed6806b2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,10 +16,11 @@ build_image:
   # description: triggers a build of the project as a Docker image,
   #              each branch will have an individual Docker image that will be used
   #              in the following stages of the pipeline for testing the code
+  image:
+    name: gitlab-registry.cern.ch/linuxsupport/cc7-base:latest
   stage: build
   tags:
-    - cvmfs
-    - docker
+    - k8s-cvmfs
   script:
     - yum -y install redhat-lsb redhat-lsb-core man uuid-devel libuuid libuuid-devel mesa-libGL-devel libXpm-devel
     - mkdir build
@@ -33,12 +34,13 @@ build_image:
     - build/
 
 test_unittest:
+  image:
+    name: gitlab-registry.cern.ch/linuxsupport/cc7-base:latest
   stage: test
   tags:
-    - cvmfs
-    - docker
+    - k8s-cvmfs
   script: 
-    - yum -y install man
+    - yum -y install man git make cmake3 gcc-c++ gcc binutils libX11-devel libXpm-devel libXft-devel libXext-devel python openssl-devel
     - cd build
     - set +e && source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh; set -e
     - set +e && asetup --input=../../calypso/asetup.faser Athena,22.0.49; set -e 
@@ -46,3 +48,6 @@ test_unittest:
     - ctest -j3
   dependencies:
     - build_image
+  artifacts:
+    paths:
+      - LastTest.log
diff --git a/Control/CalypsoExample/TruthEventDumper/python/TruthEventDumperAlg.py b/Control/CalypsoExample/TruthEventDumper/python/TruthEventDumperAlg.py
index 934433e2f375d6f0883a5a66e66dba70f1515427..8986c1165ece677bb37a42998bc2e0d05642cd8e 100644
--- a/Control/CalypsoExample/TruthEventDumper/python/TruthEventDumperAlg.py
+++ b/Control/CalypsoExample/TruthEventDumper/python/TruthEventDumperAlg.py
@@ -36,4 +36,4 @@ class TruthEventDumperAlg(PyAthena.Alg):
         # print("Mid part: ", self.maxMid, " out of ", 1000000 - 200000, " (",100*self.maxMid/(1000000-200000),"% of overflow")
         # print("Hi  part: ", self.maxHi, " out of ", (1<<31)//1000000, " (", 100*self.maxHi/((1<<31)//1000000),"% of overflow")
 
-        return StatusCode.Success
\ No newline at end of file
+        return StatusCode.Success
diff --git a/Generators/TestBeamReader/CMakeLists.txt b/Generators/TestBeamReader/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..64e5ec13d29414fcbf2468e64170029a8cf6c44b
--- /dev/null
+++ b/Generators/TestBeamReader/CMakeLists.txt
@@ -0,0 +1,11 @@
+################################################################################
+# Package: ReadTestBeam
+################################################################################
+
+# Declare the package name:
+atlas_subdir( TestBeamReader )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
+atlas_install_joboptions( share/*.py )
\ No newline at end of file
diff --git a/Generators/TestBeamReader/python/TestBeamReaderAlgH2_CMS.py b/Generators/TestBeamReader/python/TestBeamReaderAlgH2_CMS.py
new file mode 100644
index 0000000000000000000000000000000000000000..959242ab0aaa0639c672ba669b9c16400f5f726d
--- /dev/null
+++ b/Generators/TestBeamReader/python/TestBeamReaderAlgH2_CMS.py
@@ -0,0 +1,95 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+
+from AthenaCommon.AppMgr import ServiceMgr as svcMgr
+from GeneratorModules.EvgenAlg import EvgenAlg
+from AthenaPython.PyAthena import StatusCode, EventInfo, EventID, EventType
+from AthenaCommon.SystemOfUnits import GeV, m
+import ROOT
+import math
+
+
+class TestBeamReader(EvgenAlg):
+    def __init__(self, name="TestBeamReader", MCEventKey="BeamTruthEvent"):
+        super(TestBeamReader,self).__init__(name=name)
+        self.McEventKey = MCEventKey
+        return
+
+    def fillEvent(self, evt):
+        try:
+          from AthenaPython.PyAthena import HepMC3  as HepMC
+        except ImportError:
+          from AthenaPython.PyAthena import HepMC   as HepMC
+        evt.weights().push_back(1.0)
+        eventNumber = 0
+        treeEventId = 0
+        runNumber = 100
+        mcEventType = EventType()
+        mcEventType.add_type(EventType.IS_SIMULATION)
+
+        mcEventId = EventID(run_number = runNumber, event_number = eventNumber)
+        mcEventInfo = EventInfo(id = mcEventId, type = mcEventType)
+        self.evtStore.record(mcEventInfo, "McEventInfo", True, False)
+        ROOT.SetOwnership(mcEventType, False)
+        ROOT.SetOwnership(mcEventId, False)
+        ROOT.SetOwnership(mcEventInfo, False)
+        pos = HepMC.FourVector(13.8*0.001*m, 22.7*0.001*m, 0*m, 0)
+        gv = HepMC.GenVertex(pos)
+        ROOT.SetOwnership(gv, False)
+        evt.add_vertex(gv)
+
+        nParticles = list(self.evtStore["N"])
+
+        pdgclol = list(self.evtStore["vecPDG"])
+        px =list(self.evtStore["vecPx"])
+        py = list(self.evtStore["vecPy"])
+        pz = list(self.evtStore["vecPz"])
+       
+        for i in range(nParticles[0]):
+            gp = HepMC.GenParticle()
+            pdgc = int()
+
+            pdgc = math.floor(pdgclol[i])
+            if (pdgc ==-11 or pdgc ==11):
+                 M = (0.511*0.001)
+            elif (pdgc == 22):
+                 M = 0.0
+            else:
+                 M = 0.0
+            E = math.sqrt(((px[i])*(px[i]))+((py[i])*(py[i]))+((pz[i])*(pz[i]))+((M)*(M)))  
+            mom = HepMC.FourVector(px[i]*0.001*GeV, py[i]*0.001*GeV, pz[i]*0.001*GeV, E*0.001*GeV)            
+            gp.set_momentum(mom)
+            gp.set_generated_mass(M*GeV)
+            gp.set_pdg_id(pdgc)
+
+            hepmc_status = 4
+            gp.set_status(hepmc_status)
+            ROOT.SetOwnership(gp, False)
+            gv.add_particle_in(gp)
+            
+         
+
+        for i in range(nParticles[0]):
+            gp = HepMC.GenParticle()
+
+            pdgc = int()
+            pdgc = math.floor(pdgclol[i])
+            if (pdgc ==-11 or pdgc ==11):
+                 M = (0.511*0.001)
+            elif (pdgc == 22):
+                 M = 0.0
+            else:
+                 M = 0.0
+            E = math.sqrt(((px[i])*(px[i]))+((py[i])*(py[i]))+((pz[i])*(pz[i]))+((M)*(M)))
+            mom = HepMC.FourVector(px[i]*0.001*GeV, py[i]*0.001*GeV, pz[i]*0.001*GeV, E*0.001*GeV)
+            gp.set_momentum(mom)
+            gp.set_generated_mass(M*GeV)
+            gp.set_pdg_id(pdgc)
+                                                                       
+            hepmc_status = 1
+            gp.set_status(hepmc_status)
+            ROOT.SetOwnership(gp, False)
+                                                                                           
+            gv.add_particle_out(gp)    
+
+
+        return StatusCode.Success
diff --git a/Generators/TestBeamReader/python/__init__.py b/Generators/TestBeamReader/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d13ae164caa4e93bf2899cea4e67d0ec515784a2
--- /dev/null
+++ b/Generators/TestBeamReader/python/__init__.py
@@ -0,0 +1 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
diff --git a/Generators/TestBeamReader/share/Numpy_H2.py b/Generators/TestBeamReader/share/Numpy_H2.py
new file mode 100644
index 0000000000000000000000000000000000000000..9644c3d584d8157b8aa6b1acdcba8d1b7fad5b32
--- /dev/null
+++ b/Generators/TestBeamReader/share/Numpy_H2.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python                                                                                                                         
+
+# Set up (Py)ROOT.                                                                                                                            
+import ROOT
+import datetime
+from array import array
+import math
+import datetime
+import pickle
+import pandas
+import uproot3
+import uproot
+import numpy as np
+import awkward as ak
+t1 = uproot3.open("/eos/project/f/faser-preshower/simulations/H2_beamline/1M_events/FASER_e-_v46_80GeV.root")["NTuples/GoodParticle"]
+
+
+FASERdf= t1.pandas.df(["FASER_Px", "FASER_Py","FASER_Pz" ,"FASER_PDGid", "FASER_EventID", "FASER_x", "FASER_y"] )
+pandas.set_option("display.max_rows", None, "display.max_columns", None)
+
+
+FASERdf_new = FASERdf.groupby('FASER_EventID').agg(list)
+
+
+FASERdf_new =FASERdf_new.drop(FASERdf_new.index[0])
+
+
+data = {key: FASERdf_new[key].values for key in [ "FASER_Px", "FASER_Py","FASER_Pz" ,"FASER_PDGid", "FASER_x", "FASER_y"]}
+
+a = ak.Array(data["FASER_Px"])
+print(a)
+#ta =a 
+b = ak.Array(data["FASER_Py"])
+print(b)
+c = ak.Array(data["FASER_Pz"])
+print(c)
+d = ak.Array(data["FASER_PDGid"])
+print(d)
+e = ak.Array(data["FASER_x"])
+print(e)
+f = ak.Array(data["FASER_y"])
+print(f)
+
+with uproot.recreate("80GeV_first.root") as rootfile:
+     rootfile["tree1"] = ak.zip({"FASER_Px": a, "FASER_Py": b, "FASER_Pz": c, "FASER_PDGid":d, "FASER_x":e, "FASER_y": f})
+
+f = ROOT.TFile.Open("80GeV_e-_calypso_H2input.root", "recreate")
+t = ROOT.TTree("t","t")
+
+f1 = ROOT.TFile("80GeV_first.root")
+tree1 = f1.Get("tree1")
+
+
+
+
+vecPx = ROOT.std.vector(float)()
+vecPy = ROOT.std.vector(float)()
+vecPz = ROOT.std.vector(float)()
+vecPDG = ROOT.std.vector(float)()
+Num = ROOT.std.vector(int)()
+t.Branch("vecPx", vecPx)
+t.Branch("vecPy", vecPy)
+t.Branch("vecPz", vecPz)
+t.Branch("vecPDG", vecPDG)
+t.Branch("N", Num)
+print(tree1.GetEntries())
+for x in range(tree1.GetEntries()):
+     tree1.GetEntry(x)
+     px= list(tree1.FASER_Px)
+     py= list(tree1.FASER_Py)
+     pz=list(tree1.FASER_Pz)
+     pdg=list(tree1.FASER_PDGid)
+     n = (tree1.n)
+     j = 0
+     vecPx.clear()
+     vecPy.clear()
+     vecPz.clear()
+     vecPDG.clear()
+     Num.clear()
+     for j in range(n):
+      vecPx.push_back(px[j])
+      vecPy.push_back(py[j])
+      vecPz.push_back(pz[j])
+      vecPDG.push_back(pdg[j])
+      Num.push_back(n)                                                                                          
+     t.Fill()
+
+
+f.Write()
+f.Close()
diff --git a/Generators/TestBeamReader/share/TestBeamReader_jobOptionsH2_CMS.py b/Generators/TestBeamReader/share/TestBeamReader_jobOptionsH2_CMS.py
new file mode 100644
index 0000000000000000000000000000000000000000..3656ceefec3cfa79d329341d492e2f9c7e0e0e29
--- /dev/null
+++ b/Generators/TestBeamReader/share/TestBeamReader_jobOptionsH2_CMS.py
@@ -0,0 +1,39 @@
+#
+#  Usage: athena.py -c'INPUT=["/eos/project/f/faser-preshower/simulations/H2_beamline/1M_events/FASER_e-_v46_100GeV.root"]; OUTPUT="100GeV.H2.EVNT.pool.root"' TestBeamReader/TestBeamReader_jobOptionsH2_CMS.py
+#  INPUT and OUTPUT are mandatory (INPUT can be a list, as shown above)
+#  EVTMAX can be omitted; then all input files are read to the end
+#
+
+if not 'INPUT' in dir():
+    print("Missing INPUT parameter")
+    exit()
+
+if not isinstance(INPUT, (list,tuple)):
+    INPUT = [INPUT]
+    pass
+
+if not 'OUTPUT' in dir():
+    print("Missing OUTPUT parameter")
+    exit()
+
+import AthenaRootComps.ReadAthenaRoot
+
+svcMgr.EventSelector.InputCollections = INPUT
+svcMgr.EventSelector.TupleName = "t"
+
+from TestBeamReader.TestBeamReaderAlgH2_CMS import TestBeamReader
+
+from AthenaCommon.AlgSequence import AlgSequence
+job = AlgSequence()
+job += TestBeamReader()
+
+from AthenaPoolCnvSvc.WriteAthenaPool import AthenaPoolOutputStream
+ostream = AthenaPoolOutputStream( "StreamEVGEN" , OUTPUT, noTag=True )
+ostream.ItemList.remove("EventInfo#*")
+ostream.ItemList += [ "EventInfo#McEventInfo", 
+                      "McEventCollection#*" ]
+print(ostream.ItemList)
+if not 'EVTMAX' in dir():
+    EVTMAX = -1
+
+theApp.EvtMax = EVTMAX
diff --git a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
index 1fc059dc4de07df69f939839e78e3b7e2cea627e..0e88d1e46f6d84ed87bda1b669c9f4ca35aa3ea6 100644
--- a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
+++ b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.cxx
@@ -225,6 +225,9 @@ StatusCode NtupleDumperAlg::initialize()
   m_tree->Branch("longTracks", &m_longTracks, "longTracks/I");
   m_tree->Branch("Track_Chi2", &m_Chi2);
   m_tree->Branch("Track_nDoF", &m_DoF);
+  m_tree->Branch("Track_module_eta0", &m_module_eta0);
+  m_tree->Branch("Track_module_phi0", &m_module_phi0);
+  m_tree->Branch("Track_hitSet", &m_hitSet);
   m_tree->Branch("Track_x0", &m_xup);
   m_tree->Branch("Track_y0", &m_yup);
   m_tree->Branch("Track_z0", &m_zup);
@@ -912,16 +915,20 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
     std::set<std::pair<int, int>> layerMap;
     std::set<int> stationMap;
     // Check for hit in the three downstream stations
+    HitSet hitSet {24};
     for (auto measurement : *(track->measurementsOnTrack())) {
         const Tracker::FaserSCT_ClusterOnTrack* cluster = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*>(measurement);
         if (cluster != nullptr) {
             Identifier id = cluster->identify();
             int station = m_sctHelper->station(id);
             int layer = m_sctHelper->layer(id);
+            int side = m_sctHelper->side(id);
             stationMap.emplace(station);
             layerMap.emplace(station, layer);
+            hitSet.set(23 - (station * 6 + layer * 2 + side));
         }
     }
+    m_hitSet.push_back(hitSet.to_ulong());
     if (stationMap.count(1) == 0 || stationMap.count(2) == 0 || stationMap.count(3) == 0) continue;
 
     const Trk::TrackParameters* upstreamParameters = track->trackParameters()->front();
@@ -933,6 +940,12 @@ StatusCode NtupleDumperAlg::execute(const EventContext &ctx) const
 
     m_Chi2.push_back(track->fitQuality()->chiSquared());
     m_DoF.push_back(track->fitQuality()->numberDoF());
+    const Trk::MeasurementBase *measurement = track->measurementsOnTrack()->front();
+    const Tracker::FaserSCT_ClusterOnTrack* cluster =
+      dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*>(measurement);
+    Identifier id = cluster->identify();
+    m_module_eta0.push_back(m_sctHelper->eta_module(id));
+    m_module_phi0.push_back(m_sctHelper->phi_module(id));
 
     m_nHit0.push_back(stationMap.count(0));
     m_nHit1.push_back(stationMap.count(1));
@@ -1418,6 +1431,9 @@ NtupleDumperAlg::clearTree() const
 
   m_Chi2.clear();
   m_DoF.clear();
+  m_module_eta0.clear();
+  m_module_phi0.clear();
+  m_hitSet.clear();
   m_charge.clear();
   m_nLayers.clear();
   m_longTracks = 0;
diff --git a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
index 475378c18625a5c359c4f8c8ef8d52e48ee96c87..a741bcbe55097ba754a256fe3804f0e6cc170db9 100644
--- a/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
+++ b/PhysicsAnalysis/NtupleDumper/src/NtupleDumperAlg.h
@@ -24,10 +24,13 @@
 #include "xAODEventInfo/EventInfo.h"
 #include "StoreGate/ReadDecorHandle.h"
 #include "FaserActsVertexing/IVertexingTool.h"
+#include <boost/dynamic_bitset.hpp>
 
 #include <vector>
 #include <nlohmann/json.hpp>
 
+using HitSet = boost::dynamic_bitset<>;
+
 class TTree;
 class TH1;
 class FaserSCT_ID;
@@ -225,6 +228,9 @@ private:
 
   mutable int    m_longTracks;
   mutable int m_propagationError;
+  mutable std::vector<int> m_module_eta0;
+  mutable std::vector<int> m_module_phi0;
+  mutable std::vector<unsigned long> m_hitSet;
   mutable std::vector<double> m_Chi2;
   mutable std::vector<double> m_DoF;
   mutable std::vector<double> m_xup;
diff --git a/Tracking/Acts/FaserActsGeometry/CMakeLists.txt b/Tracking/Acts/FaserActsGeometry/CMakeLists.txt
index e15adcec74c6efaee85c11e388517683262c6522..58c40ebec78ecb3ab4585b9dc744392c4aa19042 100755
--- a/Tracking/Acts/FaserActsGeometry/CMakeLists.txt
+++ b/Tracking/Acts/FaserActsGeometry/CMakeLists.txt
@@ -56,6 +56,8 @@ atlas_add_component( FaserActsGeometry
                          src/FaserActsAlignmentCondAlg.cxx
                          src/NominalAlignmentCondAlg.cxx
                          src/FaserActsVolumeMappingTool.cxx
+                         src/FaserActsGeometryBoundaryTestAlg.h
+                         src/FaserActsGeometryBoundaryTestAlg.cxx
                          src/components/*.cxx
                          PUBLIC_HEADERS FaserActsGeometry
                          INCLUDE_DIRS ${CLHEP_INCLUDE_DIRS} ${EIGEN_INCLUDE_DIRS} ${BOOST_INCLUDE_DIRS}
@@ -84,3 +86,7 @@ atlas_install_headers( FaserActsGeometry )
 atlas_install_python_modules( python/*.py )
 atlas_install_scripts( test/*.py )
 
+atlas_add_test( FaserActsGeometryBoundary_test
+                SCRIPT python ${CMAKE_CURRENT_SOURCE_DIR}/test/FaserActsGeometryBoundary_test.py
+                PROPERTIES WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+                PROPERTIES TIMEOUT 300 )
diff --git a/Tracking/Acts/FaserActsGeometry/src/FaserActsGeometryBoundaryTestAlg.cxx b/Tracking/Acts/FaserActsGeometry/src/FaserActsGeometryBoundaryTestAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..57c1e3a2fe68bea133d167ea00dd539edfc0f3fe
--- /dev/null
+++ b/Tracking/Acts/FaserActsGeometry/src/FaserActsGeometryBoundaryTestAlg.cxx
@@ -0,0 +1,97 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS and FASER collaborations
+*/
+
+
+#include "FaserActsGeometryBoundaryTestAlg.h"
+#include "FaserActsGeometry/FaserActsGeometryContext.h"
+#include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Surfaces/PlaneSurface.hpp"
+#include "Acts/Surfaces/RectangleBounds.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Geometry/DetectorElementBase.hpp"
+#include "Acts/Geometry/TrackingVolume.hpp"
+
+
+FaserActsGeometryBoundaryTestAlg::FaserActsGeometryBoundaryTestAlg(const std::string &name, ISvcLocator *pSvcLocator)
+    : AthReentrantAlgorithm(name, pSvcLocator) {}
+
+
+StatusCode FaserActsGeometryBoundaryTestAlg::initialize() {
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode FaserActsGeometryBoundaryTestAlg::execute(const EventContext &ctx) const {
+
+  std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
+      = m_trackingGeometryTool->trackingGeometry();
+
+  const Acts::GeometryContext gctx =
+      m_trackingGeometryTool->getGeometryContext(ctx).context();
+
+  // loop over all tracking layer and check if contained detector elements are insie boundaries
+  // return StatusCode::Failure so that test fails, if the edge of any detector element is outside boundaries
+  const Acts::TrackingVolume *trackingVolume = trackingGeometry->highestTrackingVolume();
+  for (auto volume : trackingVolume->confinedVolumes()->arrayObjects()) {
+    for (const auto& layer : volume->confinedLayers()->arrayObjects()) {
+      if (layer->layerType() == Acts::LayerType::active) {
+        // get inner and outer boundaries from approach descriptor:
+        // - innerBoundary is approach suface with minimum z position
+        // - outerBoundary is approach suface with maximum z position
+        Acts::SurfaceVector approachSurfaces = layer->approachDescriptor()->containedSurfaces();
+        const Acts::Surface *innerApproachSurface = *std::min_element(
+            approachSurfaces.begin(), approachSurfaces.end(),
+            [&gctx](const auto &lhs, const auto &rhs) {
+                return lhs->center(gctx).z() < rhs->center(gctx).z();
+            }
+        );
+        const Acts::Surface *outerApproachSurface = *std::max_element(
+            approachSurfaces.begin(), approachSurfaces.end(),
+            [&gctx](const auto &lhs, const auto &rhs) {
+                return lhs->center(gctx).z() < rhs->center(gctx).z();
+            }
+        );
+        double zInnerBoundary = innerApproachSurface->center(gctx).z();
+        double zOuterBoundary = outerApproachSurface->center(gctx).z();
+
+        // loop over surface array and check if all edges are between inner and outer boundary
+        for (const Acts::Surface *surface : layer->surfaceArray()->surfaces()) {
+          auto planeSurface = dynamic_cast<const Acts::PlaneSurface *>(surface);
+          // make sure surface has a associated detector element (there are other active detector elements 
+          // e.g. containing material or at the tracking volume boundaries)
+          if (surface->associatedDetectorElement() != nullptr) {
+            auto bounds = dynamic_cast<const Acts::RectangleBounds*>(&surface->bounds());
+            const Acts::Vector2 min = bounds->min();
+            const Acts::Vector2 max = bounds->max();
+            // create dummpy momentum vector: local to global transformation requires  momentum vector,
+            // which is ignored for PlaneSurface
+            Acts::Vector3 dummyMomentum {1., 1., 1.};
+            // get global position at all edges of the surface
+            std::vector<Acts::Vector3> edges {};
+            edges.push_back(planeSurface->localToGlobal(gctx, {min.x(), min.y()}, dummyMomentum));
+            edges.push_back(planeSurface->localToGlobal(gctx, {min.x(), max.y()}, dummyMomentum));
+            edges.push_back(planeSurface->localToGlobal(gctx, {max.x(), min.y()}, dummyMomentum));
+            edges.push_back(planeSurface->localToGlobal(gctx, {max.x(), max.y()}, dummyMomentum));
+            for (const Acts::Vector3 &edgePosition : edges) {
+              if ((edgePosition.z() < zInnerBoundary) || (edgePosition.z() > zOuterBoundary)) {
+                std::cout << "?? surface outside boundaries\n";
+                std::cout << "inner Boundary: " << zInnerBoundary << std::endl;
+                std::cout << "outer Boundary: " << zOuterBoundary << std::endl;
+                std::cout << "edge: " << edgePosition.x() << ", " << edgePosition.y() << ", " << edgePosition.z() << std::endl;
+                return StatusCode::FAILURE;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode FaserActsGeometryBoundaryTestAlg::finalize() {
+  return StatusCode::SUCCESS;
+}
diff --git a/Tracking/Acts/FaserActsGeometry/src/FaserActsGeometryBoundaryTestAlg.h b/Tracking/Acts/FaserActsGeometry/src/FaserActsGeometryBoundaryTestAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..b2c0bf3c73b35ac1d0b9857a8c0aae72cfb3c56c
--- /dev/null
+++ b/Tracking/Acts/FaserActsGeometry/src/FaserActsGeometryBoundaryTestAlg.h
@@ -0,0 +1,25 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS and FASER collaborations
+*/
+
+#ifndef FASERACTSGEOMETRY_FASERACTSGEOMETRYBOUNDARYTESTALG_H
+#define FASERACTSGEOMETRY_FASERACTSGEOMETRYBOUNDARYTESTALG_H
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+
+
+class FaserActsGeometryBoundaryTestAlg : public AthReentrantAlgorithm {
+public:
+  FaserActsGeometryBoundaryTestAlg(const std::string &name, ISvcLocator *pSvcLocator);
+  virtual StatusCode initialize() override final;
+  virtual StatusCode execute(const EventContext &ctx) const override final;
+  virtual StatusCode finalize() override final;
+
+private:
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+    this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+};
+
+
+#endif // FASERACTSGEOMETRY_FASERACTSGEOMETRYBOUNDARYTESTALG_H
\ No newline at end of file
diff --git a/Tracking/Acts/FaserActsGeometry/src/FaserActsLayerBuilder.cxx b/Tracking/Acts/FaserActsGeometry/src/FaserActsLayerBuilder.cxx
index c5f6a68ab01b8da95f3d62149d23bdf41684263d..649f2503cb69c93b96b8e454727998656898eccf 100644
--- a/Tracking/Acts/FaserActsGeometry/src/FaserActsLayerBuilder.cxx
+++ b/Tracking/Acts/FaserActsGeometry/src/FaserActsLayerBuilder.cxx
@@ -206,7 +206,8 @@ FaserActsLayerBuilder::buildLayers(const Acts::GeometryContext& gctx,
   std::shared_ptr<const Acts::ProtoSurfaceMaterial> materialProxy = nullptr;
 
   double layerZ = 0.5 * (pl.min(Acts::binZ) + pl.max(Acts::binZ));
-  double layerThickness = (pl.max(Acts::binZ) - pl.min(Acts::binZ));
+  // increase layerThickness by 4 mm so that after alignment shifts all modules are still within the boundaries
+  double layerThickness = (pl.max(Acts::binZ) - pl.min(Acts::binZ)) + 4_mm;
   double layerZInner = layerZ - layerThickness/2.;
   double layerZOuter = layerZ + layerThickness/2.;
 
diff --git a/Tracking/Acts/FaserActsGeometry/src/components/FaserActsGeometry_entries.cxx b/Tracking/Acts/FaserActsGeometry/src/components/FaserActsGeometry_entries.cxx
index e7b7fb2fe49208911b0d98c2ba4a6d6b960df89f..a103c7a9466eb66446036cc2978e82dba8889fee 100755
--- a/Tracking/Acts/FaserActsGeometry/src/components/FaserActsGeometry_entries.cxx
+++ b/Tracking/Acts/FaserActsGeometry/src/components/FaserActsGeometry_entries.cxx
@@ -16,6 +16,7 @@
 #include "../FaserActsMaterialMapping.h"
 #include "../FaserActsSurfaceMappingTool.h"
 #include "../FaserActsMaterialTrackWriterSvc.h"
+#include "../FaserActsGeometryBoundaryTestAlg.h"
 
 DECLARE_COMPONENT( FaserActsTrackingGeometrySvc )
 DECLARE_COMPONENT( FaserActsTrackingGeometryTool )
@@ -30,3 +31,4 @@ DECLARE_COMPONENT( FaserActsMaterialJsonWriterTool )
 DECLARE_COMPONENT( FaserActsMaterialTrackWriterSvc )
 DECLARE_COMPONENT( FaserActsMaterialMapping )
 DECLARE_COMPONENT( FaserActsSurfaceMappingTool )
+DECLARE_COMPONENT( FaserActsGeometryBoundaryTestAlg )
diff --git a/Tracking/Acts/FaserActsGeometry/test/FaserActsGeometryBoundary_test.py b/Tracking/Acts/FaserActsGeometry/test/FaserActsGeometryBoundary_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6a841642b17b6ecd412fc55ced498524ece9ca5
--- /dev/null
+++ b/Tracking/Acts/FaserActsGeometry/test/FaserActsGeometryBoundary_test.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+"""
+Copyright (C) 2002-2023 CERN for the benefit of the ATLAS and FASER collaboration
+"""
+
+
+def FaserActsGeometryBoundaryTestCfg(flags, name="FaserActsGeometryBoundaryTestAlg", **kwargs):
+
+    from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+    from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg
+    from FaserActsGeometry.ActsGeometryConfig import ActsTrackingGeometryToolCfg
+    from AthenaConfiguration.ComponentFactory import CompFactory
+
+    acc = FaserSCT_GeometryCfg(flags)
+    acc.merge(MagneticFieldSvcCfg(flags))
+    result, actsTrackingGeometryTool = ActsTrackingGeometryToolCfg(flags)
+    test_alg = CompFactory.FaserActsGeometryBoundaryTestAlg
+    test_alg.TrackingGeometryTool = actsTrackingGeometryTool
+    acc.merge(result)
+    acc.addEventAlgo(test_alg(name, **kwargs))
+    return acc
+
+
+if __name__ == "__main__":
+    
+    import sys
+    from AthenaCommon.Configurable import Configurable
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    from FaserGeoModel.FaserGeoModelConfig import FaserGeometryCfg
+
+    Configurable.configurableRun3Behavior = True
+    ConfigFlags.Input.isMC = False
+    ConfigFlags.GeoModel.Align.Dynamic = False
+    ConfigFlags.IOVDb.DatabaseInstance = "CONDBR3"
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-04"
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03"
+    ConfigFlags.Detector.GeometryFaserSCT = True
+    ConfigFlags.lock()
+
+    acc = MainServicesCfg(ConfigFlags)
+    acc.merge(FaserGeometryCfg(ConfigFlags))
+    acc.merge(FaserActsGeometryBoundaryTestCfg(ConfigFlags))
+
+    replicaSvc = acc.getService("DBReplicaSvc")
+    replicaSvc.COOLSQLiteVetoPattern = ""
+    replicaSvc.UseCOOLSQLite = True
+    replicaSvc.UseCOOLFrontier = False
+    replicaSvc.UseGeomSQLite = True
+
+    sys.exit(int(acc.run(maxEvents=1).isFailure()))