invarlock 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. invarlock/__init__.py +1 -1
  2. invarlock/_data/runtime/tiers.yaml +61 -0
  3. invarlock/adapters/hf_loading.py +97 -0
  4. invarlock/calibration/__init__.py +6 -0
  5. invarlock/calibration/spectral_null.py +301 -0
  6. invarlock/calibration/variance_ve.py +154 -0
  7. invarlock/cli/app.py +15 -0
  8. invarlock/cli/commands/calibrate.py +576 -0
  9. invarlock/cli/commands/doctor.py +9 -3
  10. invarlock/cli/commands/explain_gates.py +53 -9
  11. invarlock/cli/commands/plugins.py +12 -2
  12. invarlock/cli/commands/run.py +175 -79
  13. invarlock/cli/commands/verify.py +40 -0
  14. invarlock/cli/determinism.py +237 -0
  15. invarlock/core/auto_tuning.py +215 -17
  16. invarlock/core/registry.py +9 -4
  17. invarlock/eval/bench.py +467 -141
  18. invarlock/eval/bench_regression.py +12 -0
  19. invarlock/eval/data.py +29 -7
  20. invarlock/guards/spectral.py +216 -9
  21. invarlock/guards/variance.py +6 -3
  22. invarlock/reporting/certificate.py +249 -37
  23. invarlock/reporting/certificate_schema.py +4 -1
  24. invarlock/reporting/guards_analysis.py +108 -10
  25. invarlock/reporting/normalizer.py +21 -1
  26. invarlock/reporting/policy_utils.py +100 -16
  27. {invarlock-0.3.1.dist-info → invarlock-0.3.2.dist-info}/METADATA +12 -10
  28. {invarlock-0.3.1.dist-info → invarlock-0.3.2.dist-info}/RECORD +32 -25
  29. {invarlock-0.3.1.dist-info → invarlock-0.3.2.dist-info}/WHEEL +0 -0
  30. {invarlock-0.3.1.dist-info → invarlock-0.3.2.dist-info}/entry_points.txt +0 -0
  31. {invarlock-0.3.1.dist-info → invarlock-0.3.2.dist-info}/licenses/LICENSE +0 -0
  32. {invarlock-0.3.1.dist-info → invarlock-0.3.2.dist-info}/top_level.txt +0 -0
@@ -7,9 +7,21 @@ Maps tier settings (conservative/balanced/aggressive) to specific guard paramete
7
7
  """
8
8
 
9
9
  import copy
10
+ import os
11
+ from functools import lru_cache
12
+ from importlib import resources as _ires
13
+ from pathlib import Path
10
14
  from typing import Any
11
15
 
12
- __all__ = ["resolve_tier_policies", "TIER_POLICIES", "EDIT_ADJUSTMENTS"]
16
+ import yaml
17
+
18
+ __all__ = [
19
+ "clear_tier_policies_cache",
20
+ "get_tier_policies",
21
+ "resolve_tier_policies",
22
+ "TIER_POLICIES",
23
+ "EDIT_ADJUSTMENTS",
24
+ ]
13
25
 
14
26
 
15
27
  # Base tier policy mappings
@@ -198,10 +210,183 @@ EDIT_ADJUSTMENTS: dict[str, dict[str, dict[str, Any]]] = {
198
210
  }
199
211
 
200
212
 
213
+ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
214
+ out = copy.deepcopy(base)
215
+ for key, value in override.items():
216
+ if isinstance(value, dict) and isinstance(out.get(key), dict):
217
+ out[key] = _deep_merge(out[key], value)
218
+ else:
219
+ out[key] = copy.deepcopy(value)
220
+ return out
221
+
222
+
223
+ def _load_runtime_yaml(
224
+ config_root: str | None, *rel_parts: str
225
+ ) -> dict[str, Any] | None:
226
+ """Load YAML from runtime config locations.
227
+
228
+ Search order:
229
+ 1) $INVARLOCK_CONFIG_ROOT/runtime/...
230
+ 2) invarlock._data.runtime package resources
231
+ """
232
+ if config_root:
233
+ p = Path(config_root) / "runtime"
234
+ for part in rel_parts:
235
+ p = p / part
236
+ if p.exists():
237
+ data = yaml.safe_load(p.read_text(encoding="utf-8")) or {}
238
+ if not isinstance(data, dict):
239
+ raise ValueError("Runtime YAML must be a mapping")
240
+ return data
241
+
242
+ try:
243
+ base = _ires.files("invarlock._data.runtime")
244
+ res = base
245
+ for part in rel_parts:
246
+ res = res.joinpath(part)
247
+ if getattr(res, "is_file", None) and res.is_file(): # type: ignore[attr-defined]
248
+ text = res.read_text(encoding="utf-8") # type: ignore[assignment]
249
+ data = yaml.safe_load(text) or {}
250
+ if not isinstance(data, dict):
251
+ raise ValueError("Runtime YAML must be a mapping")
252
+ return data
253
+ except Exception:
254
+ return None
255
+
256
+ return None
257
+
258
+
259
+ def _normalize_family_caps(caps: Any) -> dict[str, dict[str, float]]:
260
+ normalized: dict[str, dict[str, float]] = {}
261
+ if not isinstance(caps, dict):
262
+ return normalized
263
+ for family, value in caps.items():
264
+ family_key = str(family)
265
+ if isinstance(value, dict):
266
+ kappa = value.get("kappa")
267
+ if isinstance(kappa, int | float):
268
+ normalized[family_key] = {"kappa": float(kappa)}
269
+ elif isinstance(value, int | float):
270
+ normalized[family_key] = {"kappa": float(value)}
271
+ return normalized
272
+
273
+
274
+ def _normalize_multiple_testing(mt: Any) -> dict[str, Any]:
275
+ if not isinstance(mt, dict):
276
+ return {}
277
+ out: dict[str, Any] = {}
278
+ method = mt.get("method")
279
+ if method is not None:
280
+ out["method"] = str(method).lower()
281
+ alpha = mt.get("alpha")
282
+ try:
283
+ if alpha is not None:
284
+ out["alpha"] = float(alpha)
285
+ except Exception:
286
+ pass
287
+ m_val = mt.get("m")
288
+ try:
289
+ if m_val is not None:
290
+ out["m"] = int(m_val)
291
+ except Exception:
292
+ pass
293
+ return out
294
+
295
+
296
+ def _tier_entry_to_policy(tier_entry: dict[str, Any]) -> dict[str, dict[str, Any]]:
297
+ """Map a tiers.yaml entry to the canonical policy shape."""
298
+ out: dict[str, dict[str, Any]] = {}
299
+
300
+ metrics = tier_entry.get("metrics")
301
+ if isinstance(metrics, dict):
302
+ out["metrics"] = copy.deepcopy(metrics)
303
+
304
+ spectral_src = tier_entry.get("spectral") or tier_entry.get("spectral_guard")
305
+ if isinstance(spectral_src, dict):
306
+ spectral = copy.deepcopy(spectral_src)
307
+ if "family_caps" in spectral:
308
+ spectral["family_caps"] = _normalize_family_caps(
309
+ spectral.get("family_caps")
310
+ )
311
+ if "multiple_testing" in spectral:
312
+ spectral["multiple_testing"] = _normalize_multiple_testing(
313
+ spectral.get("multiple_testing")
314
+ )
315
+ out["spectral"] = spectral
316
+
317
+ rmt_src = tier_entry.get("rmt") or tier_entry.get("rmt_guard")
318
+ if isinstance(rmt_src, dict):
319
+ rmt = copy.deepcopy(rmt_src)
320
+ eps = rmt.get("epsilon_by_family")
321
+ if isinstance(eps, dict):
322
+ rmt["epsilon_by_family"] = {
323
+ str(k): float(v) for k, v in eps.items() if isinstance(v, int | float)
324
+ }
325
+ # Backward-compat: keep epsilon alias
326
+ rmt["epsilon"] = dict(rmt["epsilon_by_family"])
327
+ out["rmt"] = rmt
328
+
329
+ variance_src = tier_entry.get("variance") or tier_entry.get("variance_guard")
330
+ if isinstance(variance_src, dict):
331
+ out["variance"] = copy.deepcopy(variance_src)
332
+
333
+ return out
334
+
335
+
336
+ @lru_cache(maxsize=8)
337
+ def _load_tier_policies_cached(config_root: str | None) -> dict[str, dict[str, Any]]:
338
+ tiers = _load_runtime_yaml(config_root, "tiers.yaml") or {}
339
+ merged: dict[str, dict[str, Any]] = {}
340
+
341
+ # Start from defaults, then overlay tiers.yaml per-tier.
342
+ for tier_name, defaults in TIER_POLICIES.items():
343
+ merged[str(tier_name).lower()] = copy.deepcopy(defaults)
344
+
345
+ for tier_name, entry in tiers.items():
346
+ if not isinstance(entry, dict):
347
+ continue
348
+ tier_key = str(tier_name).lower()
349
+ resolved_entry = _tier_entry_to_policy(entry)
350
+ if tier_key not in merged:
351
+ merged[tier_key] = {}
352
+ merged[tier_key] = _deep_merge(merged[tier_key], resolved_entry)
353
+
354
+ return merged
355
+
356
+
357
+ def get_tier_policies(*, config_root: str | None = None) -> dict[str, dict[str, Any]]:
358
+ """Return tier policies loaded from runtime tiers.yaml (with safe defaults)."""
359
+ root = config_root
360
+ if root is None:
361
+ root = os.getenv("INVARLOCK_CONFIG_ROOT") or None
362
+ return _load_tier_policies_cached(root)
363
+
364
+
365
+ def clear_tier_policies_cache() -> None:
366
+ _load_tier_policies_cached.cache_clear()
367
+
368
+
369
+ def _load_profile_overrides(
370
+ profile: str | None, *, config_root: str | None
371
+ ) -> dict[str, Any]:
372
+ if not profile:
373
+ return {}
374
+ prof = str(profile).strip().lower()
375
+ candidate = _load_runtime_yaml(config_root, "profiles", f"{prof}.yaml")
376
+ if candidate is None and prof == "ci":
377
+ candidate = _load_runtime_yaml(config_root, "profiles", "ci_cpu.yaml") or {}
378
+ if not isinstance(candidate, dict):
379
+ return {}
380
+ return candidate
381
+
382
+
201
383
  def resolve_tier_policies(
202
384
  tier: str,
203
385
  edit_name: str | None = None,
204
386
  explicit_overrides: dict[str, dict[str, Any]] | None = None,
387
+ *,
388
+ profile: str | None = None,
389
+ config_root: str | None = None,
205
390
  ) -> dict[str, dict[str, Any]]:
206
391
  """
207
392
  Resolve tier-based guard policies with edit-specific adjustments and explicit overrides.
@@ -217,33 +402,45 @@ def resolve_tier_policies(
217
402
  Raises:
218
403
  ValueError: If tier is not recognized
219
404
  """
220
- if tier not in TIER_POLICIES:
405
+ tier_key = str(tier).lower()
406
+ tier_policies = get_tier_policies(config_root=config_root)
407
+ if tier_key not in tier_policies:
221
408
  raise ValueError(
222
- f"Unknown tier '{tier}'. Valid tiers: {list(TIER_POLICIES.keys())}"
409
+ f"Unknown tier '{tier}'. Valid tiers: {list(tier_policies.keys())}"
223
410
  )
224
411
 
225
412
  # Start with base tier policies
226
- policies: dict[str, dict[str, Any]] = copy.deepcopy(TIER_POLICIES[tier])
413
+ policies: dict[str, dict[str, Any]] = copy.deepcopy(tier_policies[tier_key])
414
+
415
+ # Apply profile overrides (when available)
416
+ overrides = _load_profile_overrides(profile, config_root=config_root)
417
+ guards = overrides.get("guards") if isinstance(overrides, dict) else None
418
+ if isinstance(guards, dict):
419
+ for guard_name, guard_overrides in guards.items():
420
+ key = str(guard_name).lower()
421
+ if not isinstance(guard_overrides, dict):
422
+ continue
423
+ if key in policies and isinstance(policies[key], dict):
424
+ policies[key] = _deep_merge(policies[key], guard_overrides)
425
+ else:
426
+ policies[key] = copy.deepcopy(guard_overrides)
227
427
 
228
428
  # Apply edit-specific adjustments
229
429
  if edit_name and edit_name in EDIT_ADJUSTMENTS:
230
430
  edit_adjustments = EDIT_ADJUSTMENTS[edit_name]
231
431
  for guard_name, adjustments in edit_adjustments.items():
232
- if guard_name in policies:
233
- guard_policy = policies[guard_name]
234
- assert isinstance(guard_policy, dict)
235
- guard_policy.update(adjustments)
432
+ if guard_name in policies and isinstance(policies.get(guard_name), dict):
433
+ policies[guard_name] = _deep_merge(policies[guard_name], adjustments)
236
434
 
237
435
  # Apply explicit overrides (highest precedence)
238
436
  if explicit_overrides:
239
437
  for guard_name, overrides in explicit_overrides.items():
240
- if guard_name in policies:
241
- guard_policy = policies[guard_name]
242
- assert isinstance(guard_policy, dict)
243
- guard_policy.update(overrides)
244
- else:
438
+ if guard_name in policies and isinstance(policies.get(guard_name), dict):
439
+ if isinstance(overrides, dict):
440
+ policies[guard_name] = _deep_merge(policies[guard_name], overrides)
441
+ elif isinstance(overrides, dict):
245
442
  # Create new guard policy if not in base tier
246
- policies[guard_name] = overrides.copy()
443
+ policies[guard_name] = copy.deepcopy(overrides)
247
444
 
248
445
  return policies
249
446
 
@@ -273,7 +470,7 @@ def get_tier_summary(tier: str, edit_name: str | None = None) -> dict[str, Any]:
273
470
  "tier": tier,
274
471
  "edit_name": edit_name,
275
472
  "error": str(e),
276
- "valid_tiers": list(TIER_POLICIES.keys()),
473
+ "valid_tiers": list(get_tier_policies().keys()),
277
474
  }
278
475
 
279
476
 
@@ -304,8 +501,9 @@ def validate_tier_config(config: Any) -> tuple[bool, str | None]:
304
501
  return False, "Missing 'tier' in auto configuration"
305
502
 
306
503
  tier = config["tier"]
307
- if tier not in TIER_POLICIES:
308
- valid_options = list(TIER_POLICIES.keys())
504
+ tier_policies = get_tier_policies()
505
+ if tier not in tier_policies:
506
+ valid_options = list(tier_policies.keys())
309
507
  return False, f"Invalid tier '{tier}'. Valid options: {valid_options}"
310
508
 
311
509
  if "enabled" in config and not isinstance(config["enabled"], bool):
@@ -196,16 +196,21 @@ class CoreRegistry:
196
196
 
197
197
  def _check_runtime_dependencies(self, deps: list[str]) -> list[str]:
198
198
  """
199
- Check if runtime dependencies are actually importable.
199
+ Check if runtime dependencies are actually present on the system.
200
+
201
+ Uses importlib.util.find_spec to avoid importing packages and triggering
202
+ heavy side effects (e.g., GPU-only extensions).
200
203
 
201
204
  Returns:
202
205
  List of missing dependency names.
203
206
  """
204
- missing = []
207
+ missing: list[str] = []
205
208
  for dep in deps:
206
209
  try:
207
- importlib.import_module(dep)
208
- except ImportError:
210
+ spec = importlib.util.find_spec(dep) # type: ignore[attr-defined]
211
+ except Exception:
212
+ spec = None
213
+ if spec is None:
209
214
  missing.append(dep)
210
215
  return missing
211
216