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.
- invarlock/__init__.py +33 -0
- invarlock/__main__.py +10 -0
- invarlock/_data/runtime/profiles/ci_cpu.yaml +15 -0
- invarlock/_data/runtime/profiles/release.yaml +23 -0
- invarlock/_data/runtime/tiers.yaml +76 -0
- invarlock/adapters/__init__.py +102 -0
- invarlock/adapters/_capabilities.py +45 -0
- invarlock/adapters/auto.py +99 -0
- invarlock/adapters/base.py +530 -0
- invarlock/adapters/base_types.py +85 -0
- invarlock/adapters/hf_bert.py +852 -0
- invarlock/adapters/hf_gpt2.py +403 -0
- invarlock/adapters/hf_llama.py +485 -0
- invarlock/adapters/hf_mixin.py +383 -0
- invarlock/adapters/hf_onnx.py +112 -0
- invarlock/adapters/hf_t5.py +137 -0
- invarlock/adapters/py.typed +1 -0
- invarlock/assurance/__init__.py +43 -0
- invarlock/cli/__init__.py +8 -0
- invarlock/cli/__main__.py +8 -0
- invarlock/cli/_evidence.py +25 -0
- invarlock/cli/_json.py +75 -0
- invarlock/cli/adapter_auto.py +162 -0
- invarlock/cli/app.py +287 -0
- invarlock/cli/commands/__init__.py +26 -0
- invarlock/cli/commands/certify.py +403 -0
- invarlock/cli/commands/doctor.py +1358 -0
- invarlock/cli/commands/explain_gates.py +151 -0
- invarlock/cli/commands/export_html.py +100 -0
- invarlock/cli/commands/plugins.py +1331 -0
- invarlock/cli/commands/report.py +354 -0
- invarlock/cli/commands/run.py +4146 -0
- invarlock/cli/commands/verify.py +1040 -0
- invarlock/cli/config.py +396 -0
- invarlock/cli/constants.py +68 -0
- invarlock/cli/device.py +92 -0
- invarlock/cli/doctor_helpers.py +74 -0
- invarlock/cli/errors.py +6 -0
- invarlock/cli/overhead_utils.py +60 -0
- invarlock/cli/provenance.py +66 -0
- invarlock/cli/utils.py +41 -0
- invarlock/config.py +56 -0
- invarlock/core/__init__.py +62 -0
- invarlock/core/abi.py +15 -0
- invarlock/core/api.py +274 -0
- invarlock/core/auto_tuning.py +317 -0
- invarlock/core/bootstrap.py +226 -0
- invarlock/core/checkpoint.py +221 -0
- invarlock/core/contracts.py +73 -0
- invarlock/core/error_utils.py +64 -0
- invarlock/core/events.py +298 -0
- invarlock/core/exceptions.py +95 -0
- invarlock/core/registry.py +481 -0
- invarlock/core/retry.py +146 -0
- invarlock/core/runner.py +2041 -0
- invarlock/core/types.py +154 -0
- invarlock/edits/__init__.py +12 -0
- invarlock/edits/_edit_utils.py +249 -0
- invarlock/edits/_external_utils.py +268 -0
- invarlock/edits/noop.py +47 -0
- invarlock/edits/py.typed +1 -0
- invarlock/edits/quant_rtn.py +801 -0
- invarlock/edits/registry.py +166 -0
- invarlock/eval/__init__.py +23 -0
- invarlock/eval/bench.py +1207 -0
- invarlock/eval/bootstrap.py +50 -0
- invarlock/eval/data.py +2052 -0
- invarlock/eval/metrics.py +2167 -0
- invarlock/eval/primary_metric.py +767 -0
- invarlock/eval/probes/__init__.py +24 -0
- invarlock/eval/probes/fft.py +139 -0
- invarlock/eval/probes/mi.py +213 -0
- invarlock/eval/probes/post_attention.py +323 -0
- invarlock/eval/providers/base.py +67 -0
- invarlock/eval/providers/seq2seq.py +111 -0
- invarlock/eval/providers/text_lm.py +113 -0
- invarlock/eval/providers/vision_text.py +93 -0
- invarlock/eval/py.typed +1 -0
- invarlock/guards/__init__.py +18 -0
- invarlock/guards/_contracts.py +9 -0
- invarlock/guards/invariants.py +640 -0
- invarlock/guards/policies.py +805 -0
- invarlock/guards/py.typed +1 -0
- invarlock/guards/rmt.py +2097 -0
- invarlock/guards/spectral.py +1419 -0
- invarlock/guards/tier_config.py +354 -0
- invarlock/guards/variance.py +3298 -0
- invarlock/guards_ref/__init__.py +15 -0
- invarlock/guards_ref/rmt_ref.py +40 -0
- invarlock/guards_ref/spectral_ref.py +135 -0
- invarlock/guards_ref/variance_ref.py +60 -0
- invarlock/model_profile.py +353 -0
- invarlock/model_utils.py +221 -0
- invarlock/observability/__init__.py +10 -0
- invarlock/observability/alerting.py +535 -0
- invarlock/observability/core.py +546 -0
- invarlock/observability/exporters.py +565 -0
- invarlock/observability/health.py +588 -0
- invarlock/observability/metrics.py +457 -0
- invarlock/observability/py.typed +1 -0
- invarlock/observability/utils.py +553 -0
- invarlock/plugins/__init__.py +12 -0
- invarlock/plugins/hello_guard.py +33 -0
- invarlock/plugins/hf_awq_adapter.py +82 -0
- invarlock/plugins/hf_bnb_adapter.py +79 -0
- invarlock/plugins/hf_gptq_adapter.py +78 -0
- invarlock/plugins/py.typed +1 -0
- invarlock/py.typed +1 -0
- invarlock/reporting/__init__.py +7 -0
- invarlock/reporting/certificate.py +3221 -0
- invarlock/reporting/certificate_schema.py +244 -0
- invarlock/reporting/dataset_hashing.py +215 -0
- invarlock/reporting/guards_analysis.py +948 -0
- invarlock/reporting/html.py +32 -0
- invarlock/reporting/normalizer.py +235 -0
- invarlock/reporting/policy_utils.py +517 -0
- invarlock/reporting/primary_metric_utils.py +265 -0
- invarlock/reporting/render.py +1442 -0
- invarlock/reporting/report.py +903 -0
- invarlock/reporting/report_types.py +278 -0
- invarlock/reporting/utils.py +175 -0
- invarlock/reporting/validate.py +631 -0
- invarlock/security.py +176 -0
- invarlock/sparsity_utils.py +323 -0
- invarlock/utils/__init__.py +150 -0
- invarlock/utils/digest.py +45 -0
- invarlock-0.2.0.dist-info/METADATA +586 -0
- invarlock-0.2.0.dist-info/RECORD +132 -0
- invarlock-0.2.0.dist-info/WHEEL +5 -0
- invarlock-0.2.0.dist-info/entry_points.txt +20 -0
- invarlock-0.2.0.dist-info/licenses/LICENSE +201 -0
- 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"]
|