invarlock 0.2.0__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 (132) hide show
  1. invarlock/__init__.py +33 -0
  2. invarlock/__main__.py +10 -0
  3. invarlock/_data/runtime/profiles/ci_cpu.yaml +15 -0
  4. invarlock/_data/runtime/profiles/release.yaml +23 -0
  5. invarlock/_data/runtime/tiers.yaml +76 -0
  6. invarlock/adapters/__init__.py +102 -0
  7. invarlock/adapters/_capabilities.py +45 -0
  8. invarlock/adapters/auto.py +99 -0
  9. invarlock/adapters/base.py +530 -0
  10. invarlock/adapters/base_types.py +85 -0
  11. invarlock/adapters/hf_bert.py +852 -0
  12. invarlock/adapters/hf_gpt2.py +403 -0
  13. invarlock/adapters/hf_llama.py +485 -0
  14. invarlock/adapters/hf_mixin.py +383 -0
  15. invarlock/adapters/hf_onnx.py +112 -0
  16. invarlock/adapters/hf_t5.py +137 -0
  17. invarlock/adapters/py.typed +1 -0
  18. invarlock/assurance/__init__.py +43 -0
  19. invarlock/cli/__init__.py +8 -0
  20. invarlock/cli/__main__.py +8 -0
  21. invarlock/cli/_evidence.py +25 -0
  22. invarlock/cli/_json.py +75 -0
  23. invarlock/cli/adapter_auto.py +162 -0
  24. invarlock/cli/app.py +287 -0
  25. invarlock/cli/commands/__init__.py +26 -0
  26. invarlock/cli/commands/certify.py +403 -0
  27. invarlock/cli/commands/doctor.py +1358 -0
  28. invarlock/cli/commands/explain_gates.py +151 -0
  29. invarlock/cli/commands/export_html.py +100 -0
  30. invarlock/cli/commands/plugins.py +1331 -0
  31. invarlock/cli/commands/report.py +354 -0
  32. invarlock/cli/commands/run.py +4146 -0
  33. invarlock/cli/commands/verify.py +1040 -0
  34. invarlock/cli/config.py +396 -0
  35. invarlock/cli/constants.py +68 -0
  36. invarlock/cli/device.py +92 -0
  37. invarlock/cli/doctor_helpers.py +74 -0
  38. invarlock/cli/errors.py +6 -0
  39. invarlock/cli/overhead_utils.py +60 -0
  40. invarlock/cli/provenance.py +66 -0
  41. invarlock/cli/utils.py +41 -0
  42. invarlock/config.py +56 -0
  43. invarlock/core/__init__.py +62 -0
  44. invarlock/core/abi.py +15 -0
  45. invarlock/core/api.py +274 -0
  46. invarlock/core/auto_tuning.py +317 -0
  47. invarlock/core/bootstrap.py +226 -0
  48. invarlock/core/checkpoint.py +221 -0
  49. invarlock/core/contracts.py +73 -0
  50. invarlock/core/error_utils.py +64 -0
  51. invarlock/core/events.py +298 -0
  52. invarlock/core/exceptions.py +95 -0
  53. invarlock/core/registry.py +481 -0
  54. invarlock/core/retry.py +146 -0
  55. invarlock/core/runner.py +2041 -0
  56. invarlock/core/types.py +154 -0
  57. invarlock/edits/__init__.py +12 -0
  58. invarlock/edits/_edit_utils.py +249 -0
  59. invarlock/edits/_external_utils.py +268 -0
  60. invarlock/edits/noop.py +47 -0
  61. invarlock/edits/py.typed +1 -0
  62. invarlock/edits/quant_rtn.py +801 -0
  63. invarlock/edits/registry.py +166 -0
  64. invarlock/eval/__init__.py +23 -0
  65. invarlock/eval/bench.py +1207 -0
  66. invarlock/eval/bootstrap.py +50 -0
  67. invarlock/eval/data.py +2052 -0
  68. invarlock/eval/metrics.py +2167 -0
  69. invarlock/eval/primary_metric.py +767 -0
  70. invarlock/eval/probes/__init__.py +24 -0
  71. invarlock/eval/probes/fft.py +139 -0
  72. invarlock/eval/probes/mi.py +213 -0
  73. invarlock/eval/probes/post_attention.py +323 -0
  74. invarlock/eval/providers/base.py +67 -0
  75. invarlock/eval/providers/seq2seq.py +111 -0
  76. invarlock/eval/providers/text_lm.py +113 -0
  77. invarlock/eval/providers/vision_text.py +93 -0
  78. invarlock/eval/py.typed +1 -0
  79. invarlock/guards/__init__.py +18 -0
  80. invarlock/guards/_contracts.py +9 -0
  81. invarlock/guards/invariants.py +640 -0
  82. invarlock/guards/policies.py +805 -0
  83. invarlock/guards/py.typed +1 -0
  84. invarlock/guards/rmt.py +2097 -0
  85. invarlock/guards/spectral.py +1419 -0
  86. invarlock/guards/tier_config.py +354 -0
  87. invarlock/guards/variance.py +3298 -0
  88. invarlock/guards_ref/__init__.py +15 -0
  89. invarlock/guards_ref/rmt_ref.py +40 -0
  90. invarlock/guards_ref/spectral_ref.py +135 -0
  91. invarlock/guards_ref/variance_ref.py +60 -0
  92. invarlock/model_profile.py +353 -0
  93. invarlock/model_utils.py +221 -0
  94. invarlock/observability/__init__.py +10 -0
  95. invarlock/observability/alerting.py +535 -0
  96. invarlock/observability/core.py +546 -0
  97. invarlock/observability/exporters.py +565 -0
  98. invarlock/observability/health.py +588 -0
  99. invarlock/observability/metrics.py +457 -0
  100. invarlock/observability/py.typed +1 -0
  101. invarlock/observability/utils.py +553 -0
  102. invarlock/plugins/__init__.py +12 -0
  103. invarlock/plugins/hello_guard.py +33 -0
  104. invarlock/plugins/hf_awq_adapter.py +82 -0
  105. invarlock/plugins/hf_bnb_adapter.py +79 -0
  106. invarlock/plugins/hf_gptq_adapter.py +78 -0
  107. invarlock/plugins/py.typed +1 -0
  108. invarlock/py.typed +1 -0
  109. invarlock/reporting/__init__.py +7 -0
  110. invarlock/reporting/certificate.py +3221 -0
  111. invarlock/reporting/certificate_schema.py +244 -0
  112. invarlock/reporting/dataset_hashing.py +215 -0
  113. invarlock/reporting/guards_analysis.py +948 -0
  114. invarlock/reporting/html.py +32 -0
  115. invarlock/reporting/normalizer.py +235 -0
  116. invarlock/reporting/policy_utils.py +517 -0
  117. invarlock/reporting/primary_metric_utils.py +265 -0
  118. invarlock/reporting/render.py +1442 -0
  119. invarlock/reporting/report.py +903 -0
  120. invarlock/reporting/report_types.py +278 -0
  121. invarlock/reporting/utils.py +175 -0
  122. invarlock/reporting/validate.py +631 -0
  123. invarlock/security.py +176 -0
  124. invarlock/sparsity_utils.py +323 -0
  125. invarlock/utils/__init__.py +150 -0
  126. invarlock/utils/digest.py +45 -0
  127. invarlock-0.2.0.dist-info/METADATA +586 -0
  128. invarlock-0.2.0.dist-info/RECORD +132 -0
  129. invarlock-0.2.0.dist-info/WHEEL +5 -0
  130. invarlock-0.2.0.dist-info/entry_points.txt +20 -0
  131. invarlock-0.2.0.dist-info/licenses/LICENSE +201 -0
  132. invarlock-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import typer
7
+ from rich.console import Console
8
+
9
+ from invarlock.core.auto_tuning import TIER_POLICIES
10
+ from invarlock.reporting.certificate import make_certificate
11
+
12
+ console = Console()
13
+
14
+
15
+ def explain_gates_command(
16
+ report: str = typer.Option(..., "--report", help="Path to primary report.json"),
17
+ baseline: str = typer.Option(
18
+ ..., "--baseline", help="Path to baseline report.json"
19
+ ),
20
+ ) -> None:
21
+ """Explain certificate gates for a report vs baseline.
22
+
23
+ Loads the reports, builds a certificate, and prints gate thresholds,
24
+ observed statistics, and pass/fail reasons in a compact, readable form.
25
+ """
26
+ report_path = Path(report)
27
+ baseline_path = Path(baseline)
28
+ if not report_path.exists() or not baseline_path.exists():
29
+ console.print("[red]Missing --report or --baseline file[/red]")
30
+ raise typer.Exit(1)
31
+
32
+ try:
33
+ report_data = json.loads(report_path.read_text())
34
+ baseline_data = json.loads(baseline_path.read_text())
35
+ except Exception as exc: # noqa: BLE001
36
+ console.print(f"[red]Failed to load inputs: {exc}[/red]")
37
+ raise typer.Exit(1) from exc
38
+
39
+ cert = make_certificate(report_data, baseline_data)
40
+ validation = (
41
+ cert.get("validation", {}) if isinstance(cert.get("validation"), dict) else {}
42
+ )
43
+
44
+ # Extract tier + metric policy (floors/hysteresis)
45
+ tier = str((cert.get("auto", {}) or {}).get("tier", "balanced")).lower()
46
+ tier_thresholds = {
47
+ "conservative": 1.05,
48
+ "balanced": 1.10,
49
+ "aggressive": 1.20,
50
+ "none": 1.10,
51
+ }
52
+ limit_base = tier_thresholds.get(tier, 1.10)
53
+ metrics_policy = (
54
+ TIER_POLICIES.get(tier, {}).get("metrics", {}) if isinstance(tier, str) else {}
55
+ )
56
+ pm_policy = (
57
+ metrics_policy.get("pm_ratio", {}) if isinstance(metrics_policy, dict) else {}
58
+ )
59
+ hysteresis_ratio = float(pm_policy.get("hysteresis_ratio", 0.0))
60
+ min_tokens = int(pm_policy.get("min_tokens", 0))
61
+ limit_with_hyst = limit_base + max(0.0, hysteresis_ratio)
62
+ tokens_ok = True
63
+ telem = cert.get("telemetry", {}) if isinstance(cert.get("telemetry"), dict) else {}
64
+ try:
65
+ total_tokens = int(telem.get("preview_total_tokens", 0)) + int(
66
+ telem.get("final_total_tokens", 0)
67
+ )
68
+ tokens_ok = (min_tokens == 0) or (total_tokens >= min_tokens)
69
+ except Exception:
70
+ tokens_ok = True
71
+
72
+ # Primary-metric ratio gate explanation (ppl-like kinds shown as ratios)
73
+ ppl = cert.get("ppl", {}) if isinstance(cert.get("ppl"), dict) else {}
74
+ ratio = ppl.get("ratio_vs_baseline")
75
+ ratio_ci = ppl.get("ratio_ci")
76
+ hysteresis_applied = bool(validation.get("hysteresis_applied"))
77
+ status = "PASS" if bool(validation.get("primary_metric_acceptable")) else "FAIL"
78
+ console.print("[bold]Gate: Primary Metric vs Baseline[/bold]")
79
+ console.print(f" status: {status}")
80
+ if isinstance(ratio, int | float):
81
+ if isinstance(ratio_ci, tuple | list) and len(ratio_ci) == 2:
82
+ console.print(
83
+ f" observed: {ratio:.3f}x (CI {ratio_ci[0]:.3f}-{ratio_ci[1]:.3f})"
84
+ )
85
+ else:
86
+ console.print(f" observed: {ratio:.3f}x")
87
+ console.print(
88
+ f" threshold: ≤ {limit_base:.2f}x{(f' (+hysteresis {hysteresis_ratio:.3f})' if hysteresis_ratio else '')}"
89
+ )
90
+ console.print(
91
+ f" tokens: {'ok' if tokens_ok else 'below floor'} (token floors: min_tokens={min_tokens or 0}, total={int(telem.get('preview_total_tokens', 0)) + int(telem.get('final_total_tokens', 0)) if telem else 0})"
92
+ )
93
+ if hysteresis_applied:
94
+ console.print(
95
+ f" note: hysteresis applied → effective threshold = {limit_with_hyst:.3f}x"
96
+ )
97
+
98
+ # Dataset split visibility from report provenance
99
+ try:
100
+ split = (report_data.get("provenance", {}) or {}).get("dataset_split")
101
+ sf = (report_data.get("provenance", {}) or {}).get("split_fallback")
102
+ if split:
103
+ line = f"Dataset split: {split}"
104
+ if sf:
105
+ line += " (fallback)"
106
+ # Click echo would be ideal, but we keep consistent console printing
107
+ console.print(line)
108
+ except Exception:
109
+ pass
110
+
111
+ # Drift gate explanation
112
+ drift = ppl.get("preview_final_ratio")
113
+ drift_ci = ppl.get("drift_ci")
114
+ drift_status = (
115
+ "PASS" if bool(validation.get("preview_final_drift_acceptable")) else "FAIL"
116
+ )
117
+ console.print("\n[bold]Gate: Drift (final/preview)[/bold]")
118
+ if isinstance(drift, int | float):
119
+ if isinstance(drift_ci, tuple | list) and len(drift_ci) == 2:
120
+ console.print(
121
+ f" observed: {drift:.3f} (CI {drift_ci[0]:.3f}-{drift_ci[1]:.3f})"
122
+ )
123
+ else:
124
+ console.print(f" observed: {drift:.3f}")
125
+ console.print(" threshold: 0.95-1.05")
126
+ console.print(f" status: {drift_status}")
127
+
128
+ # Guard Overhead explanation (if present)
129
+ overhead = (
130
+ cert.get("guard_overhead", {})
131
+ if isinstance(cert.get("guard_overhead"), dict)
132
+ else {}
133
+ )
134
+ if overhead:
135
+ passed = bool(validation.get("guard_overhead_acceptable", True))
136
+ threshold = overhead.get("threshold_percent")
137
+ if not isinstance(threshold, int | float):
138
+ threshold = float(overhead.get("overhead_threshold", 0.01)) * 100.0
139
+ pct = overhead.get("overhead_percent")
140
+ ratio = overhead.get("overhead_ratio")
141
+ console.print("\n[bold]Gate: Guard Overhead[/bold]")
142
+ if isinstance(pct, int | float):
143
+ console.print(
144
+ f" observed: {pct:+.2f}%{f' ({ratio:.3f}x)' if isinstance(ratio, int | float) else ''}"
145
+ )
146
+ elif isinstance(ratio, int | float):
147
+ console.print(f" observed: {ratio:.3f}x")
148
+ else:
149
+ console.print(" observed: N/A")
150
+ console.print(f" threshold: ≤ +{float(threshold):.1f}%")
151
+ console.print(f" status: {'PASS' if passed else 'FAIL'}")
@@ -0,0 +1,100 @@
1
+ """
2
+ InvarLock HTML Export
3
+ =================
4
+
5
+ Thin wrapper over the HTML certificate renderer to make exporting
6
+ discoverable and scriptable.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import re
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ import typer
17
+ from rich.console import Console
18
+
19
+ console = Console()
20
+
21
+
22
+ def export_html_command(
23
+ input: str = typer.Option(..., "--input", "-i", help="Path to certificate JSON"),
24
+ output: str = typer.Option(..., "--output", "-o", help="Path to output HTML file"),
25
+ embed_css: bool = typer.Option(
26
+ True,
27
+ "--embed-css/--no-embed-css",
28
+ help="Inline a minimal static stylesheet (on by default)",
29
+ ),
30
+ force: bool = typer.Option(
31
+ False, "--force", help="Overwrite output file if it already exists"
32
+ ),
33
+ ) -> None:
34
+ """Render a certificate JSON to HTML.
35
+
36
+ Exit codes:
37
+ - 0: success
38
+ - 1: generic failure (IO or overwrite refusal)
39
+ - 2: validation failure (invalid certificate schema)
40
+ """
41
+ # When called programmatically, Typer's Option defaults can be OptionInfo
42
+ try: # pragma: no cover - defensive, matches other commands' pattern
43
+ from typer.models import OptionInfo as _TyperOptionInfo
44
+ except Exception: # pragma: no cover
45
+ _TyperOptionInfo = () # type: ignore[assignment]
46
+
47
+ def _coerce(value: Any) -> Any:
48
+ if isinstance(value, _TyperOptionInfo):
49
+ return value.default
50
+ return value
51
+
52
+ input = _coerce(input)
53
+ output = _coerce(output)
54
+ embed_css = bool(_coerce(embed_css))
55
+ force = bool(_coerce(force))
56
+
57
+ in_path = Path(str(input))
58
+ out_path = Path(str(output))
59
+
60
+ if out_path.exists() and not force:
61
+ console.print(
62
+ f"[red]❌ Output file already exists. Use --force to overwrite: {out_path}[/red]"
63
+ )
64
+ raise typer.Exit(1)
65
+
66
+ try:
67
+ payload = json.loads(in_path.read_text(encoding="utf-8"))
68
+ except Exception as exc:
69
+ console.print(f"[red]❌ Failed to read input JSON: {exc}[/red]")
70
+ raise typer.Exit(1) from exc
71
+
72
+ try:
73
+ from invarlock.reporting.html import render_certificate_html
74
+
75
+ html = render_certificate_html(payload)
76
+ except ValueError as exc:
77
+ # Certificate validation failed upstream
78
+ console.print(f"[red]❌ Certificate validation failed: {exc}[/red]")
79
+ raise typer.Exit(2) from exc
80
+ except Exception as exc:
81
+ console.print(f"[red]❌ Failed to render HTML: {exc}[/red]")
82
+ raise typer.Exit(1) from exc
83
+
84
+ if not embed_css:
85
+ # Strip <style>...</style> from the head to leave it bare
86
+ html = re.sub(
87
+ r"<style[^>]*>.*?</style>", "", html, flags=re.DOTALL | re.IGNORECASE
88
+ )
89
+
90
+ try:
91
+ out_path.parent.mkdir(parents=True, exist_ok=True)
92
+ out_path.write_text(html, encoding="utf-8")
93
+ except Exception as exc:
94
+ console.print(f"[red]❌ Failed to write output file: {exc}[/red]")
95
+ raise typer.Exit(1) from exc
96
+
97
+ console.print(f"✅ Exported certificate HTML → {out_path}")
98
+
99
+
100
+ __all__ = ["export_html_command"]