@@ -86,7 +86,7 @@ def get_consumer_router(
8686
8787 # ── Producer-facing: receive events ──
8888
89- @router .post ("/v1/ lineage" )
89+ @router .post ("/lineage" )
9090 async def receive_lineage_event (
9191 request : Request ,
9292 x_api_key : Optional [str ] = Header (None , alias = "X-API-Key" ),
@@ -97,6 +97,13 @@ async def receive_lineage_event(
9797
9898 Compatible with the standard OpenLineage API endpoint.
9999 Accepts RunEvent, DatasetEvent, or JobEvent.
100+
101+ The router defines POST /lineage; the full path depends on
102+ the server mount:
103+ - UI server (mounted at /api/v1): POST /api/v1/lineage
104+ - REST server (root_path=/api/v1): POST /lineage
105+ When behind a reverse proxy, the proxy strips /api/v1,
106+ so external clients use POST /api/v1/lineage in both cases.
100107 """
101108 api_key = getattr (config , "consumer_api_key" , None )
102109 if not _verify_api_key (api_key , x_api_key , authorization ):
@@ -130,7 +137,7 @@ async def receive_lineage_event(
130137 logger .error (f"Failed to process event: { e } " )
131138 raise HTTPException (status_code = 500 , detail = str (e ))
132139
133- @router .post ("/v1/ lineage/batch" )
140+ @router .post ("/lineage/batch" )
134141 async def receive_lineage_batch (
135142 request : Request ,
136143 x_api_key : Optional [str ] = Header (None , alias = "X-API-Key" ),
@@ -202,6 +209,8 @@ def list_events(
202209 ):
203210 """List stored OpenLineage events with optional filtering."""
204211 ns_filter = _get_namespace_filter (get_allowed_namespaces )
212+ if namespace and ns_filter is not None and namespace not in ns_filter :
213+ return {"events" : [], "total" : 0 }
205214 events = store .get_events (
206215 namespace = namespace ,
207216 job_name = job_name ,
@@ -362,11 +371,15 @@ def list_runs(
362371 offset : int = Query (0 , ge = 0 ),
363372 ):
364373 """List runs, optionally filtered by job namespace and name."""
374+ ns_filter = _get_namespace_filter (get_allowed_namespaces )
375+ if job_namespace and ns_filter is not None and job_namespace not in ns_filter :
376+ return {"runs" : [], "total" : 0 }
365377 runs = store .get_runs (
366378 job_namespace = job_namespace ,
367379 job_name = job_name ,
368380 limit = limit ,
369381 offset = offset ,
382+ namespaces = ns_filter if not job_namespace else None ,
370383 )
371384 return {"runs" : runs , "total" : len (runs )}
372385
@@ -376,6 +389,11 @@ def get_run_detail(run_id: str):
376389 run = store .get_run_detail (run_id )
377390 if not run :
378391 raise HTTPException (status_code = 404 , detail = f"Run { run_id } not found" )
392+ ns_filter = _get_namespace_filter (get_allowed_namespaces )
393+ if ns_filter is not None :
394+ run_ns = run .get ("job_namespace" )
395+ if run_ns and run_ns not in ns_filter :
396+ raise HTTPException (status_code = 404 , detail = f"Run { run_id } not found" )
379397 return run
380398
381399 def _get_namespace_filter (ns_callable ) -> Optional [List [str ]]:
0 commit comments