security: fail closed for guest and production YOLO scope
This commit is contained in:
+84
-289
@@ -28,8 +28,10 @@ class Settings(BaseSettings):
|
||||
le=604_800,
|
||||
validation_alias="GEOINTEL_AUTH_SESSION_TTL_SECONDS",
|
||||
)
|
||||
# Guest access is an explicit demo-only capability. Production and generic
|
||||
# deployments must fail closed unless an operator deliberately enables it.
|
||||
guest_access_enabled: bool = Field(
|
||||
default=True,
|
||||
default=False,
|
||||
validation_alias="GEOINTEL_GUEST_ACCESS_ENABLED",
|
||||
)
|
||||
guest_display_name: str = Field(
|
||||
@@ -49,8 +51,6 @@ class Settings(BaseSettings):
|
||||
validation_alias="DATABASE_URL",
|
||||
)
|
||||
storage_root: str = Field(default="./storage", validation_alias="STORAGE_ROOT")
|
||||
# Analysis consumes only artifacts under storage_root. Provisioning
|
||||
# workflows that stage tiles elsewhere before ingest can opt out.
|
||||
allow_external_artifact_paths: bool = Field(
|
||||
default=False, validation_alias="GEOINTEL_ALLOW_EXTERNAL_ARTIFACT_PATHS"
|
||||
)
|
||||
@@ -88,35 +88,12 @@ class Settings(BaseSettings):
|
||||
default="https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl",
|
||||
validation_alias="SOURCE_CATALOG_STATBEL_DCAT_URL",
|
||||
)
|
||||
source_catalog_statbel_max_response_mb: int = Field(
|
||||
default=5,
|
||||
ge=1,
|
||||
le=10,
|
||||
validation_alias="SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB",
|
||||
)
|
||||
source_catalog_probe_timeout_seconds: int = Field(
|
||||
default=10,
|
||||
ge=1,
|
||||
le=60,
|
||||
validation_alias="SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS",
|
||||
)
|
||||
source_catalog_probe_max_response_mb: int = Field(
|
||||
default=2,
|
||||
ge=1,
|
||||
le=10,
|
||||
validation_alias="SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB",
|
||||
)
|
||||
source_catalog_probe_cache_ttl_seconds: int = Field(
|
||||
default=900,
|
||||
ge=0,
|
||||
le=86_400,
|
||||
validation_alias="SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS",
|
||||
)
|
||||
source_catalog_statbel_max_response_mb: int = Field(default=5, ge=1, le=10, validation_alias="SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB")
|
||||
source_catalog_probe_timeout_seconds: int = Field(default=10, ge=1, le=60, validation_alias="SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS")
|
||||
source_catalog_probe_max_response_mb: int = Field(default=2, ge=1, le=10, validation_alias="SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB")
|
||||
source_catalog_probe_cache_ttl_seconds: int = Field(default=900, ge=0, le=86_400, validation_alias="SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS")
|
||||
grb_enabled: bool = Field(default=True, validation_alias="GRB_ENABLED")
|
||||
grb_ogc_api_url: str = Field(
|
||||
default="https://geo.api.vlaanderen.be/GRB/ogc/features/v1",
|
||||
validation_alias="GRB_OGC_API_URL",
|
||||
)
|
||||
grb_ogc_api_url: str = Field(default="https://geo.api.vlaanderen.be/GRB/ogc/features/v1", validation_alias="GRB_OGC_API_URL")
|
||||
grb_min_side_m: float = Field(default=10.0, gt=0, validation_alias="GRB_MIN_SIDE_M")
|
||||
grb_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="GRB_MAX_SIDE_M")
|
||||
grb_page_size: int = Field(default=1000, ge=1, le=1000, validation_alias="GRB_PAGE_SIZE")
|
||||
@@ -124,99 +101,28 @@ class Settings(BaseSettings):
|
||||
grb_max_features: int = Field(default=150_000, ge=1, validation_alias="GRB_MAX_FEATURES")
|
||||
grb_timeout_seconds: int = Field(default=180, ge=1, le=600, validation_alias="GRB_TIMEOUT_SECONDS")
|
||||
grb_max_response_mb: int = Field(default=20, ge=1, le=100, validation_alias="GRB_MAX_RESPONSE_MB")
|
||||
grb_max_total_response_mb: int = Field(
|
||||
default=256,
|
||||
ge=1,
|
||||
le=2048,
|
||||
validation_alias="GRB_MAX_TOTAL_RESPONSE_MB",
|
||||
)
|
||||
grb_max_total_response_mb: int = Field(default=256, ge=1, le=2048, validation_alias="GRB_MAX_TOTAL_RESPONSE_MB")
|
||||
grb_cache_ttl_hours: int = Field(default=24, ge=0, le=8760, validation_alias="GRB_CACHE_TTL_HOURS")
|
||||
official_vector_enabled: bool = Field(default=True, validation_alias="OFFICIAL_VECTOR_ENABLED")
|
||||
bwk_wfs_url: str = Field(
|
||||
default="https://geo.api.vlaanderen.be/BWK/wfs",
|
||||
validation_alias="BWK_WFS_URL",
|
||||
)
|
||||
dov_soil_wfs_url: str = Field(
|
||||
default="https://www.dov.vlaanderen.be/geoserver/wfs",
|
||||
validation_alias="DOV_SOIL_WFS_URL",
|
||||
)
|
||||
official_vector_min_side_m: float = Field(
|
||||
default=10.0,
|
||||
gt=0,
|
||||
validation_alias="OFFICIAL_VECTOR_MIN_SIDE_M",
|
||||
)
|
||||
official_vector_max_side_m: float = Field(
|
||||
default=20_000.0,
|
||||
gt=0,
|
||||
validation_alias="OFFICIAL_VECTOR_MAX_SIDE_M",
|
||||
)
|
||||
official_vector_page_size: int = Field(
|
||||
default=1000,
|
||||
ge=1,
|
||||
le=2000,
|
||||
validation_alias="OFFICIAL_VECTOR_PAGE_SIZE",
|
||||
)
|
||||
official_vector_max_pages: int = Field(
|
||||
default=200,
|
||||
ge=1,
|
||||
le=1000,
|
||||
validation_alias="OFFICIAL_VECTOR_MAX_PAGES",
|
||||
)
|
||||
official_vector_max_features: int = Field(
|
||||
default=100_000,
|
||||
ge=1,
|
||||
validation_alias="OFFICIAL_VECTOR_MAX_FEATURES",
|
||||
)
|
||||
official_vector_timeout_seconds: int = Field(
|
||||
default=180,
|
||||
ge=1,
|
||||
le=600,
|
||||
validation_alias="OFFICIAL_VECTOR_TIMEOUT_SECONDS",
|
||||
)
|
||||
official_vector_max_response_mb: int = Field(
|
||||
default=20,
|
||||
ge=1,
|
||||
le=100,
|
||||
validation_alias="OFFICIAL_VECTOR_MAX_RESPONSE_MB",
|
||||
)
|
||||
official_vector_max_total_response_mb: int = Field(
|
||||
default=256,
|
||||
ge=1,
|
||||
le=2048,
|
||||
validation_alias="OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB",
|
||||
)
|
||||
official_vector_cache_ttl_hours: int = Field(
|
||||
default=24,
|
||||
ge=0,
|
||||
le=8760,
|
||||
validation_alias="OFFICIAL_VECTOR_CACHE_TTL_HOURS",
|
||||
)
|
||||
bwk_wfs_url: str = Field(default="https://geo.api.vlaanderen.be/BWK/wfs", validation_alias="BWK_WFS_URL")
|
||||
dov_soil_wfs_url: str = Field(default="https://www.dov.vlaanderen.be/geoserver/wfs", validation_alias="DOV_SOIL_WFS_URL")
|
||||
official_vector_min_side_m: float = Field(default=10.0, gt=0, validation_alias="OFFICIAL_VECTOR_MIN_SIDE_M")
|
||||
official_vector_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="OFFICIAL_VECTOR_MAX_SIDE_M")
|
||||
official_vector_page_size: int = Field(default=1000, ge=1, le=2000, validation_alias="OFFICIAL_VECTOR_PAGE_SIZE")
|
||||
official_vector_max_pages: int = Field(default=200, ge=1, le=1000, validation_alias="OFFICIAL_VECTOR_MAX_PAGES")
|
||||
official_vector_max_features: int = Field(default=100_000, ge=1, validation_alias="OFFICIAL_VECTOR_MAX_FEATURES")
|
||||
official_vector_timeout_seconds: int = Field(default=180, ge=1, le=600, validation_alias="OFFICIAL_VECTOR_TIMEOUT_SECONDS")
|
||||
official_vector_max_response_mb: int = Field(default=20, ge=1, le=100, validation_alias="OFFICIAL_VECTOR_MAX_RESPONSE_MB")
|
||||
official_vector_max_total_response_mb: int = Field(default=256, ge=1, le=2048, validation_alias="OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB")
|
||||
official_vector_cache_ttl_hours: int = Field(default=24, ge=0, le=8760, validation_alias="OFFICIAL_VECTOR_CACHE_TTL_HOURS")
|
||||
spw_picc_enabled: bool = Field(default=True, validation_alias="SPW_PICC_ENABLED")
|
||||
spw_picc_mapserver_url: str = Field(
|
||||
default=(
|
||||
"https://geoservices.wallonie.be/arcgis/rest/services/"
|
||||
"TOPOGRAPHIE/PICC_VDIFF/MapServer"
|
||||
),
|
||||
validation_alias="SPW_PICC_MAPSERVER_URL",
|
||||
)
|
||||
spw_picc_mapserver_url: str = Field(default="https://geoservices.wallonie.be/arcgis/rest/services/TOPOGRAPHIE/PICC_VDIFF/MapServer", validation_alias="SPW_PICC_MAPSERVER_URL")
|
||||
spw_flood_hazard_enabled: bool = Field(default=True, validation_alias="SPW_FLOOD_HAZARD_ENABLED")
|
||||
spw_flood_hazard_mapserver_url: str = Field(
|
||||
default=(
|
||||
"https://geoservices.wallonie.be/arcgis/rest/services/"
|
||||
"EAU/ALEA_INOND/MapServer"
|
||||
),
|
||||
validation_alias="SPW_FLOOD_HAZARD_MAPSERVER_URL",
|
||||
)
|
||||
spw_flood_hazard_mapserver_url: str = Field(default="https://geoservices.wallonie.be/arcgis/rest/services/EAU/ALEA_INOND/MapServer", validation_alias="SPW_FLOOD_HAZARD_MAPSERVER_URL")
|
||||
urbis_enabled: bool = Field(default=True, validation_alias="URBIS_ENABLED")
|
||||
urbis_wfs_url: str = Field(
|
||||
default="https://geoservices-vector.irisnet.be/geoserver/urbisvector/ows",
|
||||
validation_alias="URBIS_WFS_URL",
|
||||
)
|
||||
urbis_wfs_url: str = Field(default="https://geoservices-vector.irisnet.be/geoserver/urbisvector/ows", validation_alias="URBIS_WFS_URL")
|
||||
dhmv_enabled: bool = Field(default=True, validation_alias="DHMV_ENABLED")
|
||||
dhmv_wcs_url: str = Field(
|
||||
default="https://geo.api.vlaanderen.be/DHMV/wcs",
|
||||
validation_alias="DHMV_WCS_URL",
|
||||
)
|
||||
dhmv_wcs_url: str = Field(default="https://geo.api.vlaanderen.be/DHMV/wcs", validation_alias="DHMV_WCS_URL")
|
||||
dhmv_resolution_m: float = Field(default=5.0, ge=1.0, le=10.0, validation_alias="DHMV_RESOLUTION_M")
|
||||
dhmv_min_side_m: float = Field(default=10.0, gt=0, validation_alias="DHMV_MIN_SIDE_M")
|
||||
dhmv_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="DHMV_MAX_SIDE_M")
|
||||
@@ -224,10 +130,7 @@ class Settings(BaseSettings):
|
||||
dhmv_timeout_seconds: int = Field(default=300, ge=1, validation_alias="DHMV_TIMEOUT_SECONDS")
|
||||
dhmv_max_response_mb: int = Field(default=160, ge=1, validation_alias="DHMV_MAX_RESPONSE_MB")
|
||||
flood_hazard_enabled: bool = Field(default=True, validation_alias="FLOOD_HAZARD_ENABLED")
|
||||
flood_hazard_wcs_url: str = Field(
|
||||
default="https://geoservice.waterinfo.be/OGRK/wcs",
|
||||
validation_alias="FLOOD_HAZARD_WCS_URL",
|
||||
)
|
||||
flood_hazard_wcs_url: str = Field(default="https://geoservice.waterinfo.be/OGRK/wcs", validation_alias="FLOOD_HAZARD_WCS_URL")
|
||||
flood_hazard_resolution_m: float = Field(default=5.0, ge=2.0, le=20.0, validation_alias="FLOOD_HAZARD_RESOLUTION_M")
|
||||
flood_hazard_min_side_m: float = Field(default=10.0, gt=0, validation_alias="FLOOD_HAZARD_MIN_SIDE_M")
|
||||
flood_hazard_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="FLOOD_HAZARD_MAX_SIDE_M")
|
||||
@@ -235,138 +138,49 @@ class Settings(BaseSettings):
|
||||
flood_hazard_timeout_seconds: int = Field(default=300, ge=1, validation_alias="FLOOD_HAZARD_TIMEOUT_SECONDS")
|
||||
flood_hazard_max_response_mb: int = Field(default=160, ge=1, validation_alias="FLOOD_HAZARD_MAX_RESPONSE_MB")
|
||||
bathymetry_profiles_enabled: bool = Field(default=True, validation_alias="BATHYMETRY_PROFILES_ENABLED")
|
||||
bathymetry_profiles_layer_url: str = Field(
|
||||
default="https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0",
|
||||
validation_alias="BATHYMETRY_PROFILES_LAYER_URL",
|
||||
)
|
||||
bathymetry_watercourse_layer_url: str = Field(
|
||||
default="https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1",
|
||||
validation_alias="BATHYMETRY_WATERCOURSE_LAYER_URL",
|
||||
)
|
||||
bathymetry_profiles_page_size: int = Field(
|
||||
default=1000,
|
||||
ge=1,
|
||||
le=2000,
|
||||
validation_alias="BATHYMETRY_PROFILES_PAGE_SIZE",
|
||||
)
|
||||
bathymetry_profiles_max_features: int = Field(
|
||||
default=50_000,
|
||||
ge=1,
|
||||
le=250_000,
|
||||
validation_alias="BATHYMETRY_PROFILES_MAX_FEATURES",
|
||||
)
|
||||
bathymetry_profiles_max_pages: int = Field(
|
||||
default=200,
|
||||
ge=1,
|
||||
le=5_000,
|
||||
validation_alias="BATHYMETRY_PROFILES_MAX_PAGES",
|
||||
)
|
||||
bathymetry_profiles_timeout_seconds: int = Field(
|
||||
default=120,
|
||||
ge=1,
|
||||
le=600,
|
||||
validation_alias="BATHYMETRY_PROFILES_TIMEOUT_SECONDS",
|
||||
)
|
||||
bathymetry_profiles_max_response_mb: int = Field(
|
||||
default=32,
|
||||
ge=1,
|
||||
le=256,
|
||||
validation_alias="BATHYMETRY_PROFILES_MAX_RESPONSE_MB",
|
||||
)
|
||||
bathymetry_raster_max_pixels: int = Field(
|
||||
default=30_000_000,
|
||||
ge=1,
|
||||
validation_alias="BATHYMETRY_RASTER_MAX_PIXELS",
|
||||
)
|
||||
bathymetry_profiles_layer_url: str = Field(default="https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0", validation_alias="BATHYMETRY_PROFILES_LAYER_URL")
|
||||
bathymetry_watercourse_layer_url: str = Field(default="https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1", validation_alias="BATHYMETRY_WATERCOURSE_LAYER_URL")
|
||||
bathymetry_profiles_page_size: int = Field(default=1000, ge=1, le=2000, validation_alias="BATHYMETRY_PROFILES_PAGE_SIZE")
|
||||
bathymetry_profiles_max_features: int = Field(default=50_000, ge=1, le=250_000, validation_alias="BATHYMETRY_PROFILES_MAX_FEATURES")
|
||||
bathymetry_profiles_max_pages: int = Field(default=200, ge=1, le=5_000, validation_alias="BATHYMETRY_PROFILES_MAX_PAGES")
|
||||
bathymetry_profiles_timeout_seconds: int = Field(default=120, ge=1, le=600, validation_alias="BATHYMETRY_PROFILES_TIMEOUT_SECONDS")
|
||||
bathymetry_profiles_max_response_mb: int = Field(default=32, ge=1, le=256, validation_alias="BATHYMETRY_PROFILES_MAX_RESPONSE_MB")
|
||||
bathymetry_raster_max_pixels: int = Field(default=30_000_000, ge=1, validation_alias="BATHYMETRY_RASTER_MAX_PIXELS")
|
||||
mdk_bathymetry_probe_enabled: bool = Field(default=True, validation_alias="MDK_BATHYMETRY_PROBE_ENABLED")
|
||||
mdk_bathymetry_wcs_url: str = Field(
|
||||
default="https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs",
|
||||
validation_alias="MDK_BATHYMETRY_WCS_URL",
|
||||
)
|
||||
mdk_bathymetry_probe_timeout_seconds: int = Field(
|
||||
default=20,
|
||||
ge=1,
|
||||
le=120,
|
||||
validation_alias="MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS",
|
||||
)
|
||||
mdk_bathymetry_probe_max_response_mb: int = Field(
|
||||
default=4,
|
||||
ge=1,
|
||||
le=16,
|
||||
validation_alias="MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB",
|
||||
)
|
||||
mdk_bathymetry_wcs_url: str = Field(default="https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs", validation_alias="MDK_BATHYMETRY_WCS_URL")
|
||||
mdk_bathymetry_probe_timeout_seconds: int = Field(default=20, ge=1, le=120, validation_alias="MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS")
|
||||
mdk_bathymetry_probe_max_response_mb: int = Field(default=4, ge=1, le=16, validation_alias="MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB")
|
||||
thematic_raster_enabled: bool = Field(default=True, validation_alias="THEMATIC_RASTER_ENABLED")
|
||||
thematic_raster_wcs_url: str = Field(
|
||||
default="https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs",
|
||||
validation_alias="THEMATIC_RASTER_WCS_URL",
|
||||
)
|
||||
mdk_bathymetry_acquisition_enabled: bool = Field(
|
||||
default=False,
|
||||
validation_alias="MDK_BATHYMETRY_ACQUISITION_ENABLED",
|
||||
)
|
||||
thematic_raster_wcs_url: str = Field(default="https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs", validation_alias="THEMATIC_RASTER_WCS_URL")
|
||||
mdk_bathymetry_acquisition_enabled: bool = Field(default=False, validation_alias="MDK_BATHYMETRY_ACQUISITION_ENABLED")
|
||||
mdk_bathymetry_coverage_id: str | None = Field(default=None, validation_alias="MDK_BATHYMETRY_COVERAGE_ID")
|
||||
mdk_bathymetry_request_crs: str = Field(default="EPSG:4326", validation_alias="MDK_BATHYMETRY_REQUEST_CRS")
|
||||
mdk_bathymetry_max_bbox_deg2: float = Field(
|
||||
default=0.25,
|
||||
gt=0,
|
||||
validation_alias="MDK_BATHYMETRY_MAX_BBOX_DEG2",
|
||||
)
|
||||
mdk_bathymetry_acquisition_timeout_seconds: int = Field(
|
||||
default=120,
|
||||
ge=1,
|
||||
validation_alias="MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS",
|
||||
)
|
||||
mdk_bathymetry_acquisition_max_response_mb: int = Field(
|
||||
default=160,
|
||||
ge=1,
|
||||
validation_alias="MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB",
|
||||
)
|
||||
mdk_bathymetry_max_bbox_deg2: float = Field(default=0.25, gt=0, validation_alias="MDK_BATHYMETRY_MAX_BBOX_DEG2")
|
||||
mdk_bathymetry_acquisition_timeout_seconds: int = Field(default=120, ge=1, validation_alias="MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS")
|
||||
mdk_bathymetry_acquisition_max_response_mb: int = Field(default=160, ge=1, validation_alias="MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB")
|
||||
thematic_raster_min_side_m: float = Field(default=100.0, gt=0, validation_alias="THEMATIC_RASTER_MIN_SIDE_M")
|
||||
thematic_raster_max_side_m: float = Field(default=60_000.0, gt=0, validation_alias="THEMATIC_RASTER_MAX_SIDE_M")
|
||||
thematic_raster_max_pixels: int = Field(default=30_000_000, ge=1, validation_alias="THEMATIC_RASTER_MAX_PIXELS")
|
||||
thematic_raster_timeout_seconds: int = Field(default=300, ge=1, validation_alias="THEMATIC_RASTER_TIMEOUT_SECONDS")
|
||||
thematic_raster_max_response_mb: int = Field(default=160, ge=1, validation_alias="THEMATIC_RASTER_MAX_RESPONSE_MB")
|
||||
walous_enabled: bool = Field(default=True, validation_alias="WALOUS_ENABLED")
|
||||
walous_source_dir: str = Field(
|
||||
default="/app/storage/source-cache/walous",
|
||||
validation_alias="WALOUS_SOURCE_DIR",
|
||||
)
|
||||
walous_analysis_resolution_m: float = Field(
|
||||
default=10.0,
|
||||
ge=1.0,
|
||||
le=100.0,
|
||||
validation_alias="WALOUS_ANALYSIS_RESOLUTION_M",
|
||||
)
|
||||
walous_source_dir: str = Field(default="/app/storage/source-cache/walous", validation_alias="WALOUS_SOURCE_DIR")
|
||||
walous_analysis_resolution_m: float = Field(default=10.0, ge=1.0, le=100.0, validation_alias="WALOUS_ANALYSIS_RESOLUTION_M")
|
||||
walous_max_side_m: float = Field(default=60_000.0, gt=0, validation_alias="WALOUS_MAX_SIDE_M")
|
||||
walous_max_pixels: int = Field(default=36_000_000, ge=1, validation_alias="WALOUS_MAX_PIXELS")
|
||||
spw_terrain_enabled: bool = Field(default=True, validation_alias="SPW_TERRAIN_ENABLED")
|
||||
spw_terrain_source_dir: str = Field(
|
||||
default="/app/storage/source-cache/spw-terrain",
|
||||
validation_alias="SPW_TERRAIN_SOURCE_DIR",
|
||||
)
|
||||
spw_terrain_analysis_resolution_m: float = Field(
|
||||
default=5.0,
|
||||
ge=1.0,
|
||||
le=10.0,
|
||||
validation_alias="SPW_TERRAIN_ANALYSIS_RESOLUTION_M",
|
||||
)
|
||||
spw_terrain_source_dir: str = Field(default="/app/storage/source-cache/spw-terrain", validation_alias="SPW_TERRAIN_SOURCE_DIR")
|
||||
spw_terrain_analysis_resolution_m: float = Field(default=5.0, ge=1.0, le=10.0, validation_alias="SPW_TERRAIN_ANALYSIS_RESOLUTION_M")
|
||||
spw_terrain_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="SPW_TERRAIN_MAX_SIDE_M")
|
||||
spw_terrain_max_pixels: int = Field(default=12_000_000, ge=1, validation_alias="SPW_TERRAIN_MAX_PIXELS")
|
||||
redis_url: str | None = Field(default=None, validation_alias="REDIS_URL")
|
||||
log_level: str = Field(default="INFO", validation_alias="GEOINTEL_LOG_LEVEL")
|
||||
sql_log_level: str = Field(default="WARNING", validation_alias="GEOINTEL_SQL_LOG_LEVEL")
|
||||
reconcile_interrupted_runs_on_startup: bool = Field(
|
||||
default=False,
|
||||
validation_alias="GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP",
|
||||
)
|
||||
reconcile_interrupted_runs_on_startup: bool = Field(default=False, validation_alias="GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP")
|
||||
aoi_worker_enabled: bool = Field(default=False, validation_alias="GEOINTEL_AOI_WORKER_ENABLED")
|
||||
aoi_worker_poll_seconds: float = Field(default=2.0, ge=0.5, le=60.0, validation_alias="GEOINTEL_AOI_WORKER_POLL_SECONDS")
|
||||
# Executes queued detection.run / segmentation.run jobs so tiled GPU
|
||||
# inference never blocks an HTTP request.
|
||||
analysis_worker_enabled: bool = Field(default=False, validation_alias="GEOINTEL_ANALYSIS_WORKER_ENABLED")
|
||||
analysis_worker_poll_seconds: float = Field(
|
||||
default=2.0, ge=0.5, le=60.0, validation_alias="GEOINTEL_ANALYSIS_WORKER_POLL_SECONDS"
|
||||
)
|
||||
analysis_worker_poll_seconds: float = Field(default=2.0, ge=0.5, le=60.0, validation_alias="GEOINTEL_ANALYSIS_WORKER_POLL_SECONDS")
|
||||
database_statement_timeout_ms: int = Field(default=5_000, validation_alias="DATABASE_STATEMENT_TIMEOUT_MS")
|
||||
yolo_enabled: bool = Field(default=False, validation_alias="YOLO_ENABLED")
|
||||
yolo_models_dir: str = Field(default="/app/models", validation_alias="YOLO_MODELS_DIR")
|
||||
@@ -375,18 +189,12 @@ class Settings(BaseSettings):
|
||||
yolo_model_display_name: str = Field(default="Configured YOLO detector", validation_alias="YOLO_MODEL_DISPLAY_NAME")
|
||||
yolo_model_version: str | None = Field(default=None, validation_alias="YOLO_MODEL_VERSION")
|
||||
yolo_model_classes: str = Field(default="building", validation_alias="YOLO_MODEL_CLASSES")
|
||||
yolo_enforce_validation_scope: bool = Field(default=False, validation_alias="YOLO_ENFORCE_VALIDATION_SCOPE")
|
||||
yolo_validation_scope_manifest_path: str | None = Field(
|
||||
default=None,
|
||||
validation_alias="YOLO_VALIDATION_SCOPE_MANIFEST_PATH",
|
||||
)
|
||||
yolo_validation_scope_manifest_sha256: str | None = Field(
|
||||
default=None,
|
||||
validation_alias="YOLO_VALIDATION_SCOPE_MANIFEST_SHA256",
|
||||
)
|
||||
# Deprecated compatibility field. Mutable Area names are never an
|
||||
# inference authorization boundary; deployments must use the immutable
|
||||
# checksum-bound scope manifest above.
|
||||
# Serving configured YOLO is validation-scoped by default. Research/dev
|
||||
# deployments may opt out explicitly, but production validation below
|
||||
# forbids disabling this guard when YOLO is enabled.
|
||||
yolo_enforce_validation_scope: bool = Field(default=True, validation_alias="YOLO_ENFORCE_VALIDATION_SCOPE")
|
||||
yolo_validation_scope_manifest_path: str | None = Field(default=None, validation_alias="YOLO_VALIDATION_SCOPE_MANIFEST_PATH")
|
||||
yolo_validation_scope_manifest_sha256: str | None = Field(default=None, validation_alias="YOLO_VALIDATION_SCOPE_MANIFEST_SHA256")
|
||||
yolo_validated_area_names: str = Field(default="Mol,Kempen", validation_alias="YOLO_VALIDATED_AREA_NAMES")
|
||||
yolo_device: str = Field(default="cpu", validation_alias="YOLO_DEVICE")
|
||||
yolo_require_cuda: bool = Field(default=False, validation_alias="YOLO_REQUIRE_CUDA")
|
||||
@@ -394,57 +202,29 @@ class Settings(BaseSettings):
|
||||
yolo_max_tiles: int = Field(default=100, validation_alias="YOLO_MAX_TILES")
|
||||
yolo_max_detections: int = Field(default=1000, validation_alias="YOLO_MAX_DETECTIONS")
|
||||
yolo_duplicate_iou_threshold: float = Field(default=0.5, ge=0.0, le=1.0, validation_alias="YOLO_DUPLICATE_IOU_THRESHOLD")
|
||||
yolo_suppress_tile_edge_detections: bool = Field(
|
||||
default=True, validation_alias="YOLO_SUPPRESS_TILE_EDGE_DETECTIONS"
|
||||
)
|
||||
# Intersection over the smaller box. The candidate evaluation freezes this
|
||||
# during calibration; serving a promoted model at a different value means
|
||||
# the runtime suppresses detections the gate counted.
|
||||
yolo_containment_nms_threshold: float = Field(
|
||||
default=0.85, ge=0.0, le=1.0, validation_alias="YOLO_CONTAINMENT_NMS_THRESHOLD"
|
||||
)
|
||||
yolo_suppress_tile_edge_detections: bool = Field(default=True, validation_alias="YOLO_SUPPRESS_TILE_EDGE_DETECTIONS")
|
||||
yolo_containment_nms_threshold: float = Field(default=0.85, ge=0.0, le=1.0, validation_alias="YOLO_CONTAINMENT_NMS_THRESHOLD")
|
||||
yolo_batch_size: int = Field(default=1, validation_alias="YOLO_BATCH_SIZE")
|
||||
yolo_seg_enabled: bool = Field(default=False, validation_alias="YOLO_SEG_ENABLED")
|
||||
yolo_seg_model_path: str | None = Field(default=None, validation_alias="YOLO_SEG_MODEL_PATH")
|
||||
yolo_seg_model_id: str = Field(default="yolo-seg-configured", validation_alias="YOLO_SEG_MODEL_ID")
|
||||
yolo_seg_model_display_name: str = Field(
|
||||
default="Configured YOLO segmentation",
|
||||
validation_alias="YOLO_SEG_MODEL_DISPLAY_NAME",
|
||||
)
|
||||
yolo_seg_model_display_name: str = Field(default="Configured YOLO segmentation", validation_alias="YOLO_SEG_MODEL_DISPLAY_NAME")
|
||||
yolo_seg_model_version: str | None = Field(default=None, validation_alias="YOLO_SEG_MODEL_VERSION")
|
||||
sam_enabled: bool = Field(default=False, validation_alias="SAM_ENABLED")
|
||||
sam_model_path: str | None = Field(default=None, validation_alias="SAM_MODEL_PATH")
|
||||
sam_model_id: str = Field(default="sam-configured", validation_alias="SAM_MODEL_ID")
|
||||
sam_model_display_name: str = Field(
|
||||
default="Configured SAM segmentation",
|
||||
validation_alias="SAM_MODEL_DISPLAY_NAME",
|
||||
)
|
||||
sam_model_display_name: str = Field(default="Configured SAM segmentation", validation_alias="SAM_MODEL_DISPLAY_NAME")
|
||||
sam_model_version: str | None = Field(default=None, validation_alias="SAM_MODEL_VERSION")
|
||||
segmentation_max_masks_per_tile: int = Field(default=300, ge=1, validation_alias="SEGMENTATION_MAX_MASKS_PER_TILE")
|
||||
# Masks and boxes overlap differently, so segmentation carries its own
|
||||
# containment value rather than borrowing the detector's.
|
||||
segmentation_containment_nms_threshold: float = Field(
|
||||
default=0.85,
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
validation_alias="SEGMENTATION_CONTAINMENT_NMS_THRESHOLD",
|
||||
)
|
||||
segmentation_duplicate_iou_threshold: float = Field(
|
||||
default=0.5,
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
validation_alias="SEGMENTATION_DUPLICATE_IOU_THRESHOLD",
|
||||
)
|
||||
segmentation_containment_nms_threshold: float = Field(default=0.85, ge=0.0, le=1.0, validation_alias="SEGMENTATION_CONTAINMENT_NMS_THRESHOLD")
|
||||
segmentation_duplicate_iou_threshold: float = Field(default=0.5, ge=0.0, le=1.0, validation_alias="SEGMENTATION_DUPLICATE_IOU_THRESHOLD")
|
||||
ollama_enabled: bool = Field(default=False, validation_alias="OLLAMA_ENABLED")
|
||||
ollama_base_url: str = Field(default="http://127.0.0.1:11434", validation_alias="OLLAMA_BASE_URL")
|
||||
ollama_default_model: str = Field(default="qwen3.5:9b", validation_alias="OLLAMA_DEFAULT_MODEL")
|
||||
ollama_timeout_seconds: int = Field(default=120, ge=5, le=600, validation_alias="OLLAMA_TIMEOUT_SECONDS")
|
||||
ollama_max_output_tokens: int = Field(default=1_200, ge=100, le=4_000, validation_alias="OLLAMA_MAX_OUTPUT_TOKENS")
|
||||
ollama_context_tokens: int = Field(default=16_384, ge=4_096, le=131_072, validation_alias="OLLAMA_CONTEXT_TOKENS")
|
||||
cors_origins: list[str] | str = Field(
|
||||
default=["http://localhost:5173", "http://127.0.0.1:5173"],
|
||||
validation_alias="CORS_ORIGINS",
|
||||
)
|
||||
cors_origins: list[str] | str = Field(default=["http://localhost:5173", "http://127.0.0.1:5173"], validation_alias="CORS_ORIGINS")
|
||||
|
||||
@field_validator("cors_origins", mode="before")
|
||||
@classmethod
|
||||
@@ -466,18 +246,33 @@ class Settings(BaseSettings):
|
||||
return normalized
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_operator_auth(self) -> "Settings":
|
||||
def validate_operator_auth_and_production_ai(self) -> "Settings":
|
||||
self.guest_display_name = self.guest_display_name.strip()
|
||||
if not self.guest_display_name:
|
||||
raise ValueError("GEOINTEL_GUEST_DISPLAY_NAME must not be blank")
|
||||
if not self.auth_enabled:
|
||||
return self
|
||||
if not (self.auth_username or "").strip():
|
||||
raise ValueError("GEOINTEL_AUTH_USERNAME is required when authentication is enabled")
|
||||
if not (self.auth_password_hash or "").startswith("pbkdf2_sha256$"):
|
||||
raise ValueError("GEOINTEL_AUTH_PASSWORD_HASH must be a PBKDF2-SHA256 hash")
|
||||
if len(self.auth_session_secret or "") < 32:
|
||||
raise ValueError("GEOINTEL_AUTH_SESSION_SECRET must contain at least 32 characters")
|
||||
if self.auth_enabled:
|
||||
if not (self.auth_username or "").strip():
|
||||
raise ValueError("GEOINTEL_AUTH_USERNAME is required when authentication is enabled")
|
||||
if not (self.auth_password_hash or "").startswith("pbkdf2_sha256$"):
|
||||
raise ValueError("GEOINTEL_AUTH_PASSWORD_HASH must be a PBKDF2-SHA256 hash")
|
||||
if len(self.auth_session_secret or "") < 32:
|
||||
raise ValueError("GEOINTEL_AUTH_SESSION_SECRET must contain at least 32 characters")
|
||||
|
||||
production = self.app_env.strip().lower() in {"prod", "production"}
|
||||
if production and self.yolo_enabled:
|
||||
if not self.yolo_enforce_validation_scope:
|
||||
raise ValueError(
|
||||
"Production YOLO requires GEOINTEL_YOLO_ENFORCE_VALIDATION_SCOPE=true"
|
||||
)
|
||||
if not (self.yolo_validation_scope_manifest_path or "").strip():
|
||||
raise ValueError(
|
||||
"Production YOLO requires GEOINTEL_YOLO_VALIDATION_SCOPE_MANIFEST_PATH"
|
||||
)
|
||||
manifest_hash = (self.yolo_validation_scope_manifest_sha256 or "").strip().lower()
|
||||
if len(manifest_hash) != 64 or any(ch not in "0123456789abcdef" for ch in manifest_hash):
|
||||
raise ValueError(
|
||||
"Production YOLO requires a 64-character SHA-256 validation scope hash"
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user