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,32 @@
1
+ """
2
+ Minimal HTML exporter for certificates.
3
+
4
+ This implementation wraps the Markdown rendering in a simple HTML template so
5
+ that the numbers and core content remain identical across formats.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from html import escape
11
+ from typing import Any
12
+
13
+ from .render import render_certificate_markdown
14
+
15
+
16
+ def render_certificate_html(certificate: dict[str, Any]) -> str:
17
+ """Render a certificate as a simple HTML document.
18
+
19
+ Uses the Markdown renderer and embeds the content in a <pre> block to ensure
20
+ stable parity for snapshot tests without extra dependencies.
21
+ """
22
+ md = render_certificate_markdown(certificate)
23
+ body = f'<pre class="invarlock-md">{escape(md)}</pre>'
24
+ return (
25
+ '<!DOCTYPE html><html><head><meta charset="utf-8">'
26
+ "<title>InvarLock Safety Certificate</title>"
27
+ "<style>body{font-family:ui-monospace,Menlo,monospace;white-space:pre-wrap}</style>"
28
+ "</head><body>" + body + "</body></html>"
29
+ )
30
+
31
+
32
+ __all__ = ["render_certificate_html"]
@@ -0,0 +1,235 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from datetime import datetime
5
+ from typing import Any, cast
6
+
7
+ from .report_types import (
8
+ Artifacts,
9
+ DataConfig,
10
+ EditDeltas,
11
+ EditInfo,
12
+ EvalMetrics,
13
+ Flags,
14
+ GuardReport,
15
+ MetaData,
16
+ RunReport,
17
+ )
18
+
19
+
20
+ def _str(x: Any, default: str = "") -> str:
21
+ try:
22
+ s = str(x)
23
+ return s if s is not None else default
24
+ except Exception:
25
+ return default
26
+
27
+
28
+ def _as_mapping(x: Any) -> Mapping[str, Any]:
29
+ return x if isinstance(x, Mapping) else {}
30
+
31
+
32
+ def normalize_run_report(report: Mapping[str, Any] | RunReport) -> RunReport:
33
+ """Coerce an arbitrary report-like mapping into a canonical RunReport.
34
+
35
+ This is the single entry point for converting pre-canonical or loosely-typed
36
+ data into the strict PM-only RunReport shape used by certificate/report.
37
+ """
38
+ src = _as_mapping(report)
39
+
40
+ # ---- meta ----
41
+ meta_in = _as_mapping(src.get("meta"))
42
+ ts = _str(meta_in.get("ts") or datetime.now().isoformat())
43
+ meta_dict: dict[str, Any] = {
44
+ "model_id": _str(meta_in.get("model_id")),
45
+ "adapter": _str(meta_in.get("adapter")),
46
+ "commit": _str(meta_in.get("commit")),
47
+ "seed": int(meta_in.get("seed", 42) or 42),
48
+ "device": _str(meta_in.get("device", "cpu")),
49
+ "ts": ts,
50
+ "auto": meta_in.get("auto") if isinstance(meta_in.get("auto"), dict) else None,
51
+ }
52
+ meta = cast(MetaData, meta_dict)
53
+
54
+ # ---- data ----
55
+ data_in = _as_mapping(src.get("data"))
56
+ data_dict: dict[str, Any] = {
57
+ "dataset": _str(data_in.get("dataset")),
58
+ "split": _str(data_in.get("split", "validation")),
59
+ "seq_len": int(data_in.get("seq_len", 0) or 0),
60
+ "stride": int(data_in.get("stride", 0) or 0),
61
+ "preview_n": int(data_in.get("preview_n", 0) or 0),
62
+ "final_n": int(data_in.get("final_n", 0) or 0),
63
+ }
64
+ for k in (
65
+ "tokenizer_name",
66
+ "tokenizer_hash",
67
+ "vocab_size",
68
+ "bos_token",
69
+ "eos_token",
70
+ "pad_token",
71
+ "add_prefix_space",
72
+ "dataset_hash",
73
+ "preview_hash",
74
+ "final_hash",
75
+ "preview_total_tokens",
76
+ "final_total_tokens",
77
+ "masked_tokens_total",
78
+ "masked_tokens_preview",
79
+ "masked_tokens_final",
80
+ "loss_type",
81
+ ):
82
+ if k in data_in:
83
+ data_dict[k] = data_in.get(k)
84
+ data = cast(DataConfig, data_dict)
85
+
86
+ # ---- edit ----
87
+ edit_in = _as_mapping(src.get("edit"))
88
+ deltas_in = _as_mapping(edit_in.get("deltas"))
89
+ spars_val = deltas_in.get("sparsity")
90
+ deltas = EditDeltas(
91
+ params_changed=int(deltas_in.get("params_changed", 0) or 0),
92
+ sparsity=(float(spars_val) if isinstance(spars_val, int | float) else None),
93
+ bitwidth_map=(
94
+ deltas_in.get("bitwidth_map")
95
+ if isinstance(deltas_in.get("bitwidth_map"), dict)
96
+ else None
97
+ ),
98
+ layers_modified=int(deltas_in.get("layers_modified", 0) or 0),
99
+ )
100
+ edit = EditInfo(
101
+ name=_str(edit_in.get("name")),
102
+ plan_digest=_str(edit_in.get("plan_digest")),
103
+ deltas=deltas,
104
+ )
105
+
106
+ # ---- guards ----
107
+ guards_in = src.get("guards")
108
+ guards: list[dict[str, Any]] = []
109
+ if isinstance(guards_in, list):
110
+ guards = [g for g in guards_in if isinstance(g, dict)]
111
+
112
+ # ---- metrics ----
113
+ m_in = _as_mapping(src.get("metrics"))
114
+ pm_in = _as_mapping(m_in.get("primary_metric"))
115
+ metrics_out: dict[str, Any] = {"primary_metric": dict(pm_in)}
116
+
117
+ # accuracy-style fallback (from classification aggregates)
118
+ if not metrics_out["primary_metric"]:
119
+ cls = (
120
+ m_in.get("classification")
121
+ if isinstance(m_in.get("classification"), dict)
122
+ else None
123
+ )
124
+ if isinstance(cls, dict):
125
+ point = None
126
+ fin = cls.get("final")
127
+ if isinstance(fin, int | float):
128
+ point = float(fin)
129
+ elif isinstance(fin, dict):
130
+ num = fin.get("correct_total")
131
+ den = fin.get("total")
132
+ if (
133
+ isinstance(num, int | float)
134
+ and isinstance(den, int | float)
135
+ and float(den) > 0
136
+ ):
137
+ point = float(num) / float(den)
138
+ if isinstance(point, float):
139
+ # infer kind from model_id hint when available
140
+ model_id = _str(meta.get("model_id", "")).lower()
141
+ kind = "vqa_accuracy" if "vqa" in model_id else "accuracy"
142
+ pm_acc: dict[str, Any] = {
143
+ "kind": kind,
144
+ "unit": "accuracy",
145
+ "direction": "higher",
146
+ "aggregation_scope": "example",
147
+ "paired": True,
148
+ "gating_basis": "point",
149
+ "final": point,
150
+ }
151
+ # include n_final when available
152
+ if isinstance(fin, dict) and isinstance(fin.get("total"), int | float):
153
+ # safe: pm_acc is a plain dict
154
+ pm_acc["n_final"] = int(fin["total"])
155
+ metrics_out["primary_metric"] = pm_acc
156
+
157
+ # carry through selected non-PM fields when present
158
+ for k in (
159
+ "latency_ms_per_tok",
160
+ "latency_ms_p50",
161
+ "latency_ms_p95",
162
+ "memory_mb_peak",
163
+ "throughput_sps",
164
+ "spectral",
165
+ "rmt",
166
+ "invariants",
167
+ "bootstrap",
168
+ "reduction",
169
+ "moe",
170
+ "window_overlap_fraction",
171
+ "window_match_fraction",
172
+ "paired_windows",
173
+ "paired_delta_summary",
174
+ "window_pairing_reason",
175
+ "window_pairing_preview",
176
+ "window_pairing_final",
177
+ "total_tokens",
178
+ "preview_total_tokens",
179
+ "final_total_tokens",
180
+ ):
181
+ if k in m_in:
182
+ metrics_out[k] = m_in.get(k)
183
+ metrics = cast(EvalMetrics, metrics_out)
184
+
185
+ # ---- artifacts ----
186
+ a_in = _as_mapping(src.get("artifacts"))
187
+ artifacts_dict: dict[str, Any] = {
188
+ "events_path": _str(a_in.get("events_path")),
189
+ "logs_path": _str(a_in.get("logs_path")),
190
+ "checkpoint_path": a_in.get("checkpoint_path")
191
+ if a_in.get("checkpoint_path") is None
192
+ or isinstance(a_in.get("checkpoint_path"), str)
193
+ else None,
194
+ }
195
+ artifacts = cast(Artifacts, artifacts_dict)
196
+
197
+ # ---- flags ----
198
+ f_in = _as_mapping(src.get("flags"))
199
+ flags = cast(
200
+ Flags,
201
+ {
202
+ "guard_recovered": bool(f_in.get("guard_recovered", False)),
203
+ "rollback_reason": f_in.get("rollback_reason"),
204
+ },
205
+ )
206
+
207
+ out: RunReport = RunReport(
208
+ meta=meta,
209
+ data=data,
210
+ edit=edit,
211
+ guards=cast(list[GuardReport], guards),
212
+ metrics=metrics,
213
+ artifacts=artifacts,
214
+ flags=flags,
215
+ )
216
+
217
+ # keep evaluation_windows if provided (for deeper pairing-based features)
218
+ ew = src.get("evaluation_windows")
219
+ if isinstance(ew, dict):
220
+ out["evaluation_windows"] = ew
221
+
222
+ # keep guard_overhead if provided (for quality_overhead derivation downstream)
223
+ go = src.get("guard_overhead")
224
+ if isinstance(go, Mapping):
225
+ out["guard_overhead"] = dict(go)
226
+
227
+ # keep provenance when present (dataset_split, provider_digest, etc.)
228
+ prov = src.get("provenance")
229
+ if isinstance(prov, Mapping):
230
+ out["provenance"] = dict(prov)
231
+
232
+ return out
233
+
234
+
235
+ __all__ = ["normalize_run_report"]