zukhriddinai commited on
Commit
fa19e55
·
verified ·
1 Parent(s): a364c6d

Deploy public authenticated AutoML gateway

Browse files
execution_metadata.py CHANGED
@@ -10,6 +10,7 @@ from __future__ import annotations
10
 
11
  import json
12
  import os
 
13
  import tempfile
14
  import zipfile
15
  from datetime import datetime, timezone
@@ -22,6 +23,19 @@ from ui_contracts import DecisionSource, DecisionTrace, FeatureExtractionReport,
22
  EXECUTION_METADATA_KEY = "execution_metadata"
23
  EXECUTION_METADATA_SCHEMA_VERSION = 1
24
  APPROVED_RESEARCH_KEY = "approved_research"
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  _APPROVED_RESEARCH_FIELDS = {
26
  "schema_version",
27
  "research_plan",
@@ -39,6 +53,8 @@ _PIPELINE_CONFIG_FIELDS = {
39
  "transform_tool",
40
  "ml_tool",
41
  "image_backend",
 
 
42
  }
43
 
44
 
@@ -140,6 +156,7 @@ def make_execution_metadata(
140
  decision_traces: Sequence[DecisionTrace | Mapping[str, Any]] = (),
141
  *,
142
  approved_research: Mapping[str, Any] | None = None,
 
143
  ) -> dict[str, Any]:
144
  """Return the canonical JSON-safe execution metadata container."""
145
 
@@ -163,11 +180,16 @@ def make_execution_metadata(
163
  if approved_research
164
  else {}
165
  )
 
 
 
 
 
166
  if approved:
167
  approval_trace = DecisionTrace.from_mapping(approved["approval_trace"])
168
  if all(trace.trace_id != approval_trace.trace_id for trace in traces):
169
  traces.insert(0, approval_trace)
170
- if report is None and not traces and not approved:
171
  return {}
172
  result = {
173
  "schema_version": EXECUTION_METADATA_SCHEMA_VERSION,
@@ -176,9 +198,77 @@ def make_execution_metadata(
176
  }
177
  if approved:
178
  result[APPROVED_RESEARCH_KEY] = approved
 
 
179
  return result
180
 
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  def normalize_approved_research(value: Mapping[str, Any]) -> dict[str, Any]:
183
  """Validate the optional approved plan carried into trusted execution."""
184
 
@@ -263,6 +353,7 @@ def normalize_execution_metadata(value: Mapping[str, Any]) -> dict[str, Any]:
263
  data.get("feature_extraction_report"),
264
  traces,
265
  approved_research=data.get(APPROVED_RESEARCH_KEY),
 
266
  )
267
 
268
 
@@ -271,12 +362,18 @@ def merge_execution_metadata(
271
  feature_report: FeatureExtractionReport | Mapping[str, Any] | None = None,
272
  decision_traces: Sequence[DecisionTrace | Mapping[str, Any]] = (),
273
  approved_research: Mapping[str, Any] | None = None,
 
274
  ) -> dict[str, Any]:
275
  """Merge producer metadata without duplicating stable trace identifiers."""
276
 
277
  report = feature_report
278
  traces: list[DecisionTrace | Mapping[str, Any]] = []
279
  approved = approved_research
 
 
 
 
 
280
  for value in values:
281
  canonical = normalize_execution_metadata(value) if value else {}
282
  if canonical.get("feature_extraction_report") is not None:
@@ -284,6 +381,11 @@ def merge_execution_metadata(
284
  traces.extend(canonical.get("decision_traces", ()))
285
  if canonical.get(APPROVED_RESEARCH_KEY):
286
  approved = canonical[APPROVED_RESEARCH_KEY]
 
 
 
 
 
287
  traces.extend(decision_traces)
288
 
289
  deduplicated: dict[str, DecisionTrace] = {}
@@ -302,6 +404,7 @@ def merge_execution_metadata(
302
  report,
303
  [*deduplicated.values(), *anonymous],
304
  approved_research=approved,
 
305
  )
306
 
307
 
@@ -377,6 +480,13 @@ def execution_metadata_from_manifest(manifest: Mapping[str, Any]) -> dict[str, A
377
  return normalize_execution_metadata(_mapping(value, EXECUTION_METADATA_KEY))
378
 
379
 
 
 
 
 
 
 
 
380
  def manifest_with_execution_metadata(
381
  manifest: Mapping[str, Any], metadata: Mapping[str, Any]
382
  ) -> dict[str, Any]:
@@ -393,6 +503,7 @@ __all__ = [
393
  "EXECUTION_METADATA_KEY",
394
  "EXECUTION_METADATA_SCHEMA_VERSION",
395
  "APPROVED_RESEARCH_KEY",
 
396
  "execution_metadata_from_manifest",
397
  "make_decision_trace",
398
  "make_execution_metadata",
@@ -402,4 +513,6 @@ __all__ = [
402
  "merge_execution_metadata",
403
  "normalize_execution_metadata",
404
  "normalize_approved_research",
 
 
405
  ]
 
10
 
11
  import json
12
  import os
13
+ import re
14
  import tempfile
15
  import zipfile
16
  from datetime import datetime, timezone
 
23
  EXECUTION_METADATA_KEY = "execution_metadata"
24
  EXECUTION_METADATA_SCHEMA_VERSION = 1
25
  APPROVED_RESEARCH_KEY = "approved_research"
26
+ TRUSTED_EXTRACTOR_KEY = "trusted_extractor"
27
+ _EXTRACTOR_ID_PATTERN = re.compile(r"^[a-z][a-z0-9_]{2,63}$")
28
+ _EXTRACTOR_VERSION_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$")
29
+ _SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
30
+ _TRUSTED_EXTRACTOR_FIELDS = {
31
+ "schema_version",
32
+ "extractor_id",
33
+ "version",
34
+ "configuration",
35
+ "bundle_hash",
36
+ "required_assets",
37
+ }
38
+ _TRUSTED_ASSET_FIELDS = {"asset_id", "sha256", "size_bytes"}
39
  _APPROVED_RESEARCH_FIELDS = {
40
  "schema_version",
41
  "research_plan",
 
53
  "transform_tool",
54
  "ml_tool",
55
  "image_backend",
56
+ "image_backbone",
57
+ "extractor_configuration",
58
  }
59
 
60
 
 
156
  decision_traces: Sequence[DecisionTrace | Mapping[str, Any]] = (),
157
  *,
158
  approved_research: Mapping[str, Any] | None = None,
159
+ trusted_extractor: Mapping[str, Any] | None = None,
160
  ) -> dict[str, Any]:
161
  """Return the canonical JSON-safe execution metadata container."""
162
 
 
180
  if approved_research
181
  else {}
182
  )
183
+ extractor_binding = (
184
+ normalize_trusted_extractor_binding(trusted_extractor)
185
+ if trusted_extractor
186
+ else {}
187
+ )
188
  if approved:
189
  approval_trace = DecisionTrace.from_mapping(approved["approval_trace"])
190
  if all(trace.trace_id != approval_trace.trace_id for trace in traces):
191
  traces.insert(0, approval_trace)
192
+ if report is None and not traces and not approved and not extractor_binding:
193
  return {}
194
  result = {
195
  "schema_version": EXECUTION_METADATA_SCHEMA_VERSION,
 
198
  }
199
  if approved:
200
  result[APPROVED_RESEARCH_KEY] = approved
201
+ if extractor_binding:
202
+ result[TRUSTED_EXTRACTOR_KEY] = extractor_binding
203
  return result
204
 
205
 
206
+ def normalize_trusted_extractor_binding(value: Mapping[str, Any]) -> dict[str, Any]:
207
+ """Validate a package-safe binding without treating it as executable authority."""
208
+
209
+ data = _mapping(value, TRUSTED_EXTRACTOR_KEY)
210
+ if set(data) != _TRUSTED_EXTRACTOR_FIELDS or data.get("schema_version") != 1:
211
+ raise ValueError("trusted_extractor has an unsupported shape")
212
+ extractor_id = str(data.get("extractor_id") or "").strip().lower()
213
+ version = str(data.get("version") or "").strip()
214
+ bundle_hash = str(data.get("bundle_hash") or "").strip().lower()
215
+ if not _EXTRACTOR_ID_PATTERN.fullmatch(extractor_id):
216
+ raise ValueError("trusted_extractor.extractor_id is invalid")
217
+ if not _EXTRACTOR_VERSION_PATTERN.fullmatch(version):
218
+ raise ValueError("trusted_extractor.version is invalid")
219
+ if not _SHA256_PATTERN.fullmatch(bundle_hash):
220
+ raise ValueError("trusted_extractor.bundle_hash is invalid")
221
+ configuration = data.get("configuration")
222
+ if not isinstance(configuration, Mapping) or not all(
223
+ isinstance(key, str) for key in configuration
224
+ ):
225
+ raise ValueError("trusted_extractor.configuration must be a mapping")
226
+ try:
227
+ configuration_copy = json.loads(
228
+ json.dumps(dict(configuration), allow_nan=False)
229
+ )
230
+ except (TypeError, ValueError, OverflowError, RecursionError) as exc:
231
+ raise ValueError("trusted_extractor.configuration must be JSON-safe") from exc
232
+ if len(json.dumps(configuration_copy).encode("utf-8")) > 32_768:
233
+ raise ValueError("trusted_extractor.configuration is too large")
234
+ raw_assets = data.get("required_assets")
235
+ if not isinstance(raw_assets, (list, tuple)) or len(raw_assets) > 64:
236
+ raise ValueError("trusted_extractor.required_assets must be a bounded array")
237
+ assets: list[dict[str, Any]] = []
238
+ asset_ids: set[str] = set()
239
+ for raw in raw_assets:
240
+ asset = _mapping(raw, "trusted extractor asset")
241
+ if set(asset) != _TRUSTED_ASSET_FIELDS:
242
+ raise ValueError("trusted extractor asset has an unsupported shape")
243
+ asset_id = str(asset.get("asset_id") or "").strip()
244
+ digest = str(asset.get("sha256") or "").strip().lower()
245
+ size = asset.get("size_bytes")
246
+ if (
247
+ not asset_id
248
+ or len(asset_id) > 128
249
+ or asset_id in {".", ".."}
250
+ or "/" in asset_id
251
+ or "\\" in asset_id
252
+ or any(ord(character) < 32 for character in asset_id)
253
+ or asset_id in asset_ids
254
+ ):
255
+ raise ValueError("trusted extractor asset_id is invalid or duplicated")
256
+ if not _SHA256_PATTERN.fullmatch(digest):
257
+ raise ValueError("trusted extractor asset sha256 is invalid")
258
+ if isinstance(size, bool) or not isinstance(size, int) or size <= 0:
259
+ raise ValueError("trusted extractor asset size_bytes must be positive")
260
+ asset_ids.add(asset_id)
261
+ assets.append({"asset_id": asset_id, "sha256": digest, "size_bytes": size})
262
+ return {
263
+ "schema_version": 1,
264
+ "extractor_id": extractor_id,
265
+ "version": version,
266
+ "configuration": configuration_copy,
267
+ "bundle_hash": bundle_hash,
268
+ "required_assets": assets,
269
+ }
270
+
271
+
272
  def normalize_approved_research(value: Mapping[str, Any]) -> dict[str, Any]:
273
  """Validate the optional approved plan carried into trusted execution."""
274
 
 
353
  data.get("feature_extraction_report"),
354
  traces,
355
  approved_research=data.get(APPROVED_RESEARCH_KEY),
356
+ trusted_extractor=data.get(TRUSTED_EXTRACTOR_KEY),
357
  )
358
 
359
 
 
362
  feature_report: FeatureExtractionReport | Mapping[str, Any] | None = None,
363
  decision_traces: Sequence[DecisionTrace | Mapping[str, Any]] = (),
364
  approved_research: Mapping[str, Any] | None = None,
365
+ trusted_extractor: Mapping[str, Any] | None = None,
366
  ) -> dict[str, Any]:
367
  """Merge producer metadata without duplicating stable trace identifiers."""
368
 
369
  report = feature_report
370
  traces: list[DecisionTrace | Mapping[str, Any]] = []
371
  approved = approved_research
372
+ extractor_binding: Mapping[str, Any] | None = (
373
+ normalize_trusted_extractor_binding(trusted_extractor)
374
+ if trusted_extractor
375
+ else None
376
+ )
377
  for value in values:
378
  canonical = normalize_execution_metadata(value) if value else {}
379
  if canonical.get("feature_extraction_report") is not None:
 
381
  traces.extend(canonical.get("decision_traces", ()))
382
  if canonical.get(APPROVED_RESEARCH_KEY):
383
  approved = canonical[APPROVED_RESEARCH_KEY]
384
+ if canonical.get(TRUSTED_EXTRACTOR_KEY):
385
+ incoming_binding = canonical[TRUSTED_EXTRACTOR_KEY]
386
+ if extractor_binding is not None and dict(extractor_binding) != dict(incoming_binding):
387
+ raise ValueError("cannot merge conflicting trusted extractor bindings")
388
+ extractor_binding = incoming_binding
389
  traces.extend(decision_traces)
390
 
391
  deduplicated: dict[str, DecisionTrace] = {}
 
404
  report,
405
  [*deduplicated.values(), *anonymous],
406
  approved_research=approved,
407
+ trusted_extractor=extractor_binding,
408
  )
409
 
410
 
 
480
  return normalize_execution_metadata(_mapping(value, EXECUTION_METADATA_KEY))
481
 
482
 
483
+ def trusted_extractor_from_manifest(manifest: Mapping[str, Any]) -> dict[str, Any]:
484
+ """Read the optional package binding; legacy packages return an empty mapping."""
485
+
486
+ metadata = execution_metadata_from_manifest(manifest)
487
+ return dict(metadata.get(TRUSTED_EXTRACTOR_KEY) or {})
488
+
489
+
490
  def manifest_with_execution_metadata(
491
  manifest: Mapping[str, Any], metadata: Mapping[str, Any]
492
  ) -> dict[str, Any]:
 
503
  "EXECUTION_METADATA_KEY",
504
  "EXECUTION_METADATA_SCHEMA_VERSION",
505
  "APPROVED_RESEARCH_KEY",
506
+ "TRUSTED_EXTRACTOR_KEY",
507
  "execution_metadata_from_manifest",
508
  "make_decision_trace",
509
  "make_execution_metadata",
 
513
  "merge_execution_metadata",
514
  "normalize_execution_metadata",
515
  "normalize_approved_research",
516
+ "normalize_trusted_extractor_binding",
517
+ "trusted_extractor_from_manifest",
518
  ]
gateway_app.py CHANGED
@@ -93,6 +93,7 @@ from gateway_routing import (
93
  route_existing,
94
  route_prediction,
95
  route_uploaded,
 
96
  )
97
 
98
 
@@ -123,6 +124,7 @@ IMAGE_BACKBONE_CHOICES = [
123
  "resnet50",
124
  "densenet121",
125
  "efficientnet_b0",
 
126
  ]
127
  IMAGE_CLASSIFIER_CHOICES = [
128
  AUTO_CHOICE,
@@ -164,6 +166,7 @@ def _catalog() -> dict[str, Any]:
164
  "datasets": {
165
  "No datasets configured": {"targets": [], "image": False}
166
  },
 
167
  }
168
 
169
 
@@ -1790,6 +1793,11 @@ def existing_ui_handler(
1790
  )
1791
  if approved_research:
1792
  payload["approved_research"] = dict(approved_research)
 
 
 
 
 
1793
  yield from run_existing_remote(payload, session_token, request)
1794
 
1795
 
@@ -1885,6 +1893,11 @@ def uploaded_ui_handler(
1885
  )
1886
  if approved_research:
1887
  payload["approved_research"] = dict(approved_research)
 
 
 
 
 
1888
  yield from run_uploaded_remote(
1889
  payload, train_file, test_file, session_token, request
1890
  )
@@ -1923,7 +1936,7 @@ def parallel_existing_ui_handler(
1923
  config.get("feature_selection_tool") or AUTOMATIC_SELECTION,
1924
  config.get("transform_tool") or AUTOMATIC_SELECTION,
1925
  config.get("ml_tool") or AUTOMATIC_SELECTION,
1926
- AUTO_CHOICE,
1927
  AUTO_CHOICE,
1928
  AUTO_CHOICE,
1929
  AUTO_DEVICE,
@@ -1939,6 +1952,10 @@ def parallel_existing_ui_handler(
1939
  )
1940
  if approved_research:
1941
  payload["approved_research"] = dict(approved_research)
 
 
 
 
1942
  yield from run_existing_remote(payload, session_token, request)
1943
 
1944
 
@@ -2079,6 +2096,11 @@ def guided_run_ui_handler(
2079
  active_ml = str(
2080
  approved_config.get("ml_tool") or AUTOMATIC_SELECTION
2081
  )
 
 
 
 
 
2082
  state = _guided_snapshot_state(
2083
  initial_state,
2084
  data_source=source,
@@ -2115,7 +2137,7 @@ def guided_run_ui_handler(
2115
  active_feature,
2116
  active_transform,
2117
  active_ml,
2118
- image_backbone,
2119
  image_classifier,
2120
  image_size,
2121
  image_device,
@@ -2147,7 +2169,7 @@ def guided_run_ui_handler(
2147
  active_feature,
2148
  active_transform,
2149
  active_ml,
2150
- upload_backbone,
2151
  upload_classifier,
2152
  upload_size,
2153
  upload_device,
 
93
  route_existing,
94
  route_prediction,
95
  route_uploaded,
96
+ trusted_gateway_extractor_choices,
97
  )
98
 
99
 
 
124
  "resnet50",
125
  "densenet121",
126
  "efficientnet_b0",
127
+ *trusted_gateway_extractor_choices(),
128
  ]
129
  IMAGE_CLASSIFIER_CHOICES = [
130
  AUTO_CHOICE,
 
166
  "datasets": {
167
  "No datasets configured": {"targets": [], "image": False}
168
  },
169
+ "trusted_extractors": {},
170
  }
171
 
172
 
 
1793
  )
1794
  if approved_research:
1795
  payload["approved_research"] = dict(approved_research)
1796
+ approved_config = dict(approved_research.get("pipeline_config") or {})
1797
+ if approved_config.get("extractor_configuration") is not None:
1798
+ payload["extractor_configuration"] = dict(
1799
+ approved_config["extractor_configuration"]
1800
+ )
1801
  yield from run_existing_remote(payload, session_token, request)
1802
 
1803
 
 
1893
  )
1894
  if approved_research:
1895
  payload["approved_research"] = dict(approved_research)
1896
+ approved_config = dict(approved_research.get("pipeline_config") or {})
1897
+ if approved_config.get("extractor_configuration") is not None:
1898
+ payload["extractor_configuration"] = dict(
1899
+ approved_config["extractor_configuration"]
1900
+ )
1901
  yield from run_uploaded_remote(
1902
  payload, train_file, test_file, session_token, request
1903
  )
 
1936
  config.get("feature_selection_tool") or AUTOMATIC_SELECTION,
1937
  config.get("transform_tool") or AUTOMATIC_SELECTION,
1938
  config.get("ml_tool") or AUTOMATIC_SELECTION,
1939
+ config.get("image_backbone") or AUTO_CHOICE,
1940
  AUTO_CHOICE,
1941
  AUTO_CHOICE,
1942
  AUTO_DEVICE,
 
1952
  )
1953
  if approved_research:
1954
  payload["approved_research"] = dict(approved_research)
1955
+ if config.get("extractor_configuration") is not None:
1956
+ payload["extractor_configuration"] = dict(
1957
+ config["extractor_configuration"]
1958
+ )
1959
  yield from run_existing_remote(payload, session_token, request)
1960
 
1961
 
 
2096
  active_ml = str(
2097
  approved_config.get("ml_tool") or AUTOMATIC_SELECTION
2098
  )
2099
+ active_backbone = str(
2100
+ approved_config.get("image_backbone")
2101
+ or (image_backbone if source == "managed" else upload_backbone)
2102
+ or AUTO_CHOICE
2103
+ )
2104
  state = _guided_snapshot_state(
2105
  initial_state,
2106
  data_source=source,
 
2137
  active_feature,
2138
  active_transform,
2139
  active_ml,
2140
+ active_backbone,
2141
  image_classifier,
2142
  image_size,
2143
  image_device,
 
2169
  active_feature,
2170
  active_transform,
2171
  active_ml,
2172
+ active_backbone,
2173
  upload_classifier,
2174
  upload_size,
2175
  upload_device,
gateway_catalog.json CHANGED
@@ -37,5 +37,45 @@
37
  ]
38
  }
39
  },
40
- "schema_version": 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
 
37
  ]
38
  }
39
  },
40
+ "schema_version": 1,
41
+ "trusted_extractors": {
42
+ "trusted_tiny_onnx_v1": {
43
+ "allowed_configuration_fields": [
44
+ "image_size",
45
+ "mean",
46
+ "normalize",
47
+ "std"
48
+ ],
49
+ "devices": [
50
+ "cpu",
51
+ "cuda"
52
+ ],
53
+ "display_name": "Trusted Tiny ONNX Embedding",
54
+ "modality": "image",
55
+ "output_dimension": 8,
56
+ "tasks": [
57
+ "classification"
58
+ ],
59
+ "version": "1.0.0"
60
+ },
61
+ "trusted_tiny_torchscript_v1": {
62
+ "allowed_configuration_fields": [
63
+ "image_size",
64
+ "mean",
65
+ "normalize",
66
+ "std"
67
+ ],
68
+ "devices": [
69
+ "cpu",
70
+ "cuda"
71
+ ],
72
+ "display_name": "Trusted Tiny TorchScript Embedding",
73
+ "modality": "image",
74
+ "output_dimension": 8,
75
+ "tasks": [
76
+ "classification"
77
+ ],
78
+ "version": "1.0.0"
79
+ }
80
+ }
81
  }
gateway_contract.py CHANGED
@@ -148,6 +148,21 @@ def validate_request(kind: str, value: str | Mapping[str, Any]) -> dict[str, Any
148
  )
149
  except (TypeError, ValueError) as exc:
150
  raise ContractError("approved_research is invalid.") from exc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  elif "image_batch_size" in payload:
152
  payload["image_batch_size"] = _bounded_int(
153
  payload, "image_batch_size", 16
@@ -370,7 +385,14 @@ def load_gateway_catalog(path: str | Path) -> dict[str, Any]:
370
  raise ContractError("Gateway catalog datasets must be a JSON object.")
371
  if int(catalog.get("schema_version", 1)) != 1:
372
  raise ContractError("Unsupported gateway catalog schema version.")
373
- normalized: dict[str, Any] = {"schema_version": 1, "datasets": {}}
 
 
 
 
 
 
 
374
  for name, metadata in catalog["datasets"].items():
375
  if not isinstance(metadata, dict):
376
  raise ContractError("Gateway catalog dataset metadata must be an object.")
@@ -381,6 +403,27 @@ def load_gateway_catalog(path: str | Path) -> dict[str, Any]:
381
  "targets": targets,
382
  "image": bool(metadata.get("image", False)),
383
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  return normalized
385
 
386
 
 
148
  )
149
  except (TypeError, ValueError) as exc:
150
  raise ContractError("approved_research is invalid.") from exc
151
+ if payload.get("extractor_configuration") is not None:
152
+ configuration = payload["extractor_configuration"]
153
+ if not isinstance(configuration, Mapping) or not all(
154
+ isinstance(key, str) for key in configuration
155
+ ):
156
+ raise ContractError("extractor_configuration must be a mapping.")
157
+ try:
158
+ copied_configuration = json.loads(
159
+ json.dumps(dict(configuration), allow_nan=False)
160
+ )
161
+ except (TypeError, ValueError, OverflowError, RecursionError) as exc:
162
+ raise ContractError("extractor_configuration must be JSON-safe.") from exc
163
+ if len(json.dumps(copied_configuration).encode("utf-8")) > 32_768:
164
+ raise ContractError("extractor_configuration is too large.")
165
+ payload["extractor_configuration"] = copied_configuration
166
  elif "image_batch_size" in payload:
167
  payload["image_batch_size"] = _bounded_int(
168
  payload, "image_batch_size", 16
 
385
  raise ContractError("Gateway catalog datasets must be a JSON object.")
386
  if int(catalog.get("schema_version", 1)) != 1:
387
  raise ContractError("Unsupported gateway catalog schema version.")
388
+ raw_extractors = catalog.get("trusted_extractors", {})
389
+ if not isinstance(raw_extractors, dict):
390
+ raise ContractError("Gateway catalog trusted_extractors must be a JSON object.")
391
+ normalized: dict[str, Any] = {
392
+ "schema_version": 1,
393
+ "datasets": {},
394
+ "trusted_extractors": {},
395
+ }
396
  for name, metadata in catalog["datasets"].items():
397
  if not isinstance(metadata, dict):
398
  raise ContractError("Gateway catalog dataset metadata must be an object.")
 
403
  "targets": targets,
404
  "image": bool(metadata.get("image", False)),
405
  }
406
+ for extractor_id, metadata in raw_extractors.items():
407
+ if not isinstance(extractor_id, str) or not isinstance(metadata, dict):
408
+ raise ContractError("Gateway custom extractor metadata must be an object.")
409
+ required = {
410
+ "version",
411
+ "display_name",
412
+ "modality",
413
+ "tasks",
414
+ "devices",
415
+ "output_dimension",
416
+ "allowed_configuration_fields",
417
+ }
418
+ if set(metadata) != required:
419
+ raise ContractError("Gateway custom extractor metadata has an unsupported shape.")
420
+ if metadata["modality"] != "image":
421
+ raise ContractError("Gateway custom extractor modality must be image.")
422
+ if not isinstance(metadata["tasks"], list) or not set(metadata["tasks"]) <= {"classification"}:
423
+ raise ContractError("Gateway custom extractor tasks are invalid.")
424
+ if not isinstance(metadata["devices"], list) or not set(metadata["devices"]) <= {"cpu", "cuda"}:
425
+ raise ContractError("Gateway custom extractor devices are invalid.")
426
+ normalized["trusted_extractors"][extractor_id] = dict(metadata)
427
  return normalized
428
 
429
 
gateway_routing.py CHANGED
@@ -6,7 +6,10 @@ shipped in the source-audited public gateway without pulling in ML code.
6
 
7
  from __future__ import annotations
8
 
 
 
9
  from dataclasses import dataclass
 
10
  from typing import Any, Literal, Mapping
11
 
12
 
@@ -177,6 +180,64 @@ def _normalize(value: Any, aliases: Mapping[str, str], field: str) -> str:
177
  raise RoutingError(f"Unknown {field} value: {value!r}.") from exc
178
 
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  def _routing_mode(value: Any) -> str:
181
  mode = str(value or DUAL_ROUTING_MODE).strip().lower()
182
  if mode not in VALID_ROUTING_MODES:
@@ -193,9 +254,7 @@ def _normalized_image_fields(
193
  backend_key = "uploaded_image_backend" if uploaded else "image_backend"
194
  backend = _normalize(payload.get(backend_key), _BACKEND_ALIASES, "image backend")
195
  device = _normalize(payload.get("image_device"), _DEVICE_ALIASES, "image device")
196
- backbone = _normalize(
197
- payload.get("image_backbone"), _BACKBONE_ALIASES, "image backbone"
198
- )
199
  preset = _normalize(
200
  payload.get("cancer_preset"), _PRESET_ALIASES, "cancer preset"
201
  )
@@ -250,6 +309,19 @@ def _normalized_image_fields(
250
  if effective_backend == "medical_agent" and preset != "auto":
251
  backbone = _PRESET_BACKBONES[preset]
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  return {
254
  "task": task,
255
  "requested_backend": backend,
@@ -258,6 +330,7 @@ def _normalized_image_fields(
258
  "backbone": backbone,
259
  "preset": preset,
260
  "candidates": candidates,
 
261
  }
262
 
263
 
@@ -268,6 +341,7 @@ def _route_image(fields: Mapping[str, Any], mode: str) -> RouteDecision:
268
  device = str(fields["device"])
269
  backbone = str(fields["backbone"])
270
  candidates = tuple(fields["candidates"])
 
271
 
272
  if mode == SINGLE_GPU_ROUTING_MODE:
273
  return RouteDecision(
@@ -316,6 +390,18 @@ def _route_image(fields: Mapping[str, Any], mode: str) -> RouteDecision:
316
  "all benchmark candidates are CPU-compatible and CPU was selected",
317
  )
318
  if backend == "medical_agent":
 
 
 
 
 
 
 
 
 
 
 
 
319
  if device == "cpu":
320
  return RouteDecision(
321
  CPU_PROFILE,
@@ -329,6 +415,18 @@ def _route_image(fields: Mapping[str, Any], mode: str) -> RouteDecision:
329
  )
330
  if backend != "existing":
331
  raise RoutingError(f"Unsupported effective image backend: {backend!r}.")
 
 
 
 
 
 
 
 
 
 
 
 
332
  if backbone in CPU_ONLY_BACKBONES:
333
  return RouteDecision(
334
  CPU_PROFILE,
@@ -439,6 +537,47 @@ def route_prediction(
439
  "gpu_package_required",
440
  "this model package requires the private GPU backend",
441
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  if device == "cuda":
443
  return RouteDecision(
444
  GPU_PROFILE,
 
6
 
7
  from __future__ import annotations
8
 
9
+ import json
10
+ from functools import lru_cache
11
  from dataclasses import dataclass
12
+ from pathlib import Path
13
  from typing import Any, Literal, Mapping
14
 
15
 
 
180
  raise RoutingError(f"Unknown {field} value: {value!r}.") from exc
181
 
182
 
183
+ @lru_cache(maxsize=1)
184
+ def trusted_gateway_extractor_catalog() -> Mapping[str, Mapping[str, Any]]:
185
+ """Load the generated public-safe projection of the private registry."""
186
+
187
+ path = Path(__file__).resolve().with_name("gateway_catalog.json")
188
+ if not path.is_file():
189
+ return {}
190
+ try:
191
+ payload = json.loads(path.read_text(encoding="utf-8"))
192
+ except (OSError, UnicodeError, json.JSONDecodeError):
193
+ return {}
194
+ raw = payload.get("trusted_extractors") if isinstance(payload, Mapping) else None
195
+ if not isinstance(raw, Mapping):
196
+ return {}
197
+ result: dict[str, Mapping[str, Any]] = {}
198
+ required = {
199
+ "version",
200
+ "display_name",
201
+ "modality",
202
+ "tasks",
203
+ "devices",
204
+ "output_dimension",
205
+ "allowed_configuration_fields",
206
+ }
207
+ for extractor_id, metadata in raw.items():
208
+ normalized_id = _token(extractor_id)
209
+ if normalized_id != extractor_id or not isinstance(metadata, Mapping):
210
+ continue
211
+ if set(metadata) != required:
212
+ continue
213
+ tasks = metadata.get("tasks")
214
+ devices = metadata.get("devices")
215
+ if (
216
+ metadata.get("modality") != "image"
217
+ or not isinstance(tasks, list)
218
+ or not set(tasks) <= {"classification"}
219
+ or not isinstance(devices, list)
220
+ or not devices
221
+ or not set(devices) <= {"cpu", "cuda"}
222
+ ):
223
+ continue
224
+ result[normalized_id] = dict(metadata)
225
+ return result
226
+
227
+
228
+ def trusted_gateway_extractor_choices() -> tuple[str, ...]:
229
+ return tuple(sorted(trusted_gateway_extractor_catalog()))
230
+
231
+
232
+ def _normalize_backbone(value: Any) -> str:
233
+ token = _token(value)
234
+ if token in _BACKBONE_ALIASES:
235
+ return _BACKBONE_ALIASES[token]
236
+ if token in trusted_gateway_extractor_catalog():
237
+ return token
238
+ raise RoutingError(f"Unknown image backbone value: {value!r}.")
239
+
240
+
241
  def _routing_mode(value: Any) -> str:
242
  mode = str(value or DUAL_ROUTING_MODE).strip().lower()
243
  if mode not in VALID_ROUTING_MODES:
 
254
  backend_key = "uploaded_image_backend" if uploaded else "image_backend"
255
  backend = _normalize(payload.get(backend_key), _BACKEND_ALIASES, "image backend")
256
  device = _normalize(payload.get("image_device"), _DEVICE_ALIASES, "image device")
257
+ backbone = _normalize_backbone(payload.get("image_backbone"))
 
 
258
  preset = _normalize(
259
  payload.get("cancer_preset"), _PRESET_ALIASES, "cancer preset"
260
  )
 
309
  if effective_backend == "medical_agent" and preset != "auto":
310
  backbone = _PRESET_BACKBONES[preset]
311
 
312
+ custom_metadata = trusted_gateway_extractor_catalog().get(backbone)
313
+ if custom_metadata is not None:
314
+ if task not in custom_metadata["tasks"]:
315
+ raise RoutingError(
316
+ f"Registered custom extractor {backbone!r} does not support {task}."
317
+ )
318
+ supported_devices = set(custom_metadata["devices"])
319
+ requested_device = "cuda" if device == "cuda" else device
320
+ if requested_device != "auto" and requested_device not in supported_devices:
321
+ raise RoutingError(
322
+ f"Registered custom extractor {backbone!r} does not support {requested_device}."
323
+ )
324
+
325
  return {
326
  "task": task,
327
  "requested_backend": backend,
 
330
  "backbone": backbone,
331
  "preset": preset,
332
  "candidates": candidates,
333
+ "custom_devices": tuple(custom_metadata["devices"]) if custom_metadata else (),
334
  }
335
 
336
 
 
341
  device = str(fields["device"])
342
  backbone = str(fields["backbone"])
343
  candidates = tuple(fields["candidates"])
344
+ custom_devices = tuple(fields.get("custom_devices") or ())
345
 
346
  if mode == SINGLE_GPU_ROUTING_MODE:
347
  return RouteDecision(
 
390
  "all benchmark candidates are CPU-compatible and CPU was selected",
391
  )
392
  if backend == "medical_agent":
393
+ if custom_devices:
394
+ if device == "cpu" or (device == "auto" and "cuda" not in custom_devices):
395
+ return RouteDecision(
396
+ CPU_PROFILE,
397
+ "trusted_custom_extractor_cpu",
398
+ "the registered custom extractor is assigned to private CPU execution",
399
+ )
400
+ return RouteDecision(
401
+ GPU_PROFILE,
402
+ "trusted_custom_extractor_gpu",
403
+ "the registered custom extractor is assigned to private GPU execution",
404
+ )
405
  if device == "cpu":
406
  return RouteDecision(
407
  CPU_PROFILE,
 
415
  )
416
  if backend != "existing":
417
  raise RoutingError(f"Unsupported effective image backend: {backend!r}.")
418
+ if custom_devices:
419
+ if device == "cpu" or (device == "auto" and "cuda" not in custom_devices):
420
+ return RouteDecision(
421
+ CPU_PROFILE,
422
+ "trusted_custom_extractor_cpu",
423
+ "the registered custom extractor is assigned to private CPU execution",
424
+ )
425
+ return RouteDecision(
426
+ GPU_PROFILE,
427
+ "trusted_custom_extractor_gpu",
428
+ "the registered custom extractor is assigned to private GPU execution",
429
+ )
430
  if backbone in CPU_ONLY_BACKBONES:
431
  return RouteDecision(
432
  CPU_PROFILE,
 
537
  "gpu_package_required",
538
  "this model package requires the private GPU backend",
539
  )
540
+ execution_metadata = manifest.get("execution_metadata")
541
+ binding = (
542
+ execution_metadata.get("trusted_extractor")
543
+ if isinstance(execution_metadata, Mapping)
544
+ else None
545
+ )
546
+ if binding is not None:
547
+ if not isinstance(binding, Mapping):
548
+ raise RoutingError("Trusted extractor package binding is invalid.")
549
+ extractor_id = str(binding.get("extractor_id") or "").strip().lower()
550
+ custom_metadata = trusted_gateway_extractor_catalog().get(extractor_id)
551
+ if custom_metadata is None:
552
+ raise RoutingError("Model package references an unregistered custom extractor.")
553
+ supported_devices = set(custom_metadata["devices"])
554
+ if device == "cpu":
555
+ if "cpu" not in supported_devices:
556
+ raise RoutingError("Custom extractor package does not support CPU replay.")
557
+ return RouteDecision(
558
+ CPU_PROFILE,
559
+ "trusted_custom_package_cpu",
560
+ "registered custom extractor package replay uses private CPU",
561
+ )
562
+ if device == "cuda":
563
+ if "cuda" not in supported_devices:
564
+ raise RoutingError("Custom extractor package does not support CUDA replay.")
565
+ return RouteDecision(
566
+ GPU_PROFILE,
567
+ "trusted_custom_package_gpu",
568
+ "registered custom extractor package replay uses private GPU",
569
+ )
570
+ if "cuda" in supported_devices:
571
+ return RouteDecision(
572
+ GPU_PROFILE,
573
+ "trusted_custom_package_gpu",
574
+ "registered custom extractor package replay uses private GPU",
575
+ )
576
+ return RouteDecision(
577
+ CPU_PROFILE,
578
+ "trusted_custom_package_cpu",
579
+ "registered custom extractor package replay uses private CPU",
580
+ )
581
  if device == "cuda":
582
  return RouteDecision(
583
  GPU_PROFILE,