invarlock 0.3.3__py3-none-any.whl → 0.3.4__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.
@@ -117,6 +117,18 @@ def _validate_pairing(certificate: dict[str, Any]) -> list[str]:
117
117
 
118
118
  match_fraction = stats.get("window_match_fraction")
119
119
  overlap_fraction = stats.get("window_overlap_fraction")
120
+ pairing_reason = stats.get("window_pairing_reason")
121
+ paired_windows = _coerce_int(stats.get("paired_windows"))
122
+
123
+ if pairing_reason is not None:
124
+ errors.append(
125
+ "window_pairing_reason must be null/None for paired certificates "
126
+ f"(found {pairing_reason!r})."
127
+ )
128
+ if paired_windows is None:
129
+ errors.append("Certificate missing paired_windows metric.")
130
+ elif paired_windows == 0:
131
+ errors.append("paired_windows must be > 0 for paired certificates (found 0).")
120
132
 
121
133
  if match_fraction is None:
122
134
  errors.append("Certificate missing window_match_fraction metric.")
invarlock/core/runner.py CHANGED
@@ -1528,7 +1528,7 @@ class CoreRunner:
1528
1528
  pairing_reason = "duplicate_windows"
1529
1529
  elif count_mismatch:
1530
1530
  pairing_reason = "count_mismatch"
1531
- elif not pairing_context:
1531
+ else:
1532
1532
  pairing_reason = preview_pair_stats.get(
1533
1533
  "reason"
1534
1534
  ) or final_pair_stats.get("reason")
@@ -2079,24 +2079,49 @@ class CoreRunner:
2079
2079
  # Perform rollback if checkpoint available
2080
2080
  if self.checkpoint_manager and "initial_checkpoint" in report.meta:
2081
2081
  checkpoint_id = report.meta["initial_checkpoint"]
2082
- self.checkpoint_manager.restore_checkpoint(
2083
- model, adapter, checkpoint_id
2084
- )
2085
- # Match test expectation: only include checkpoint and reason
2086
- self._log_event(
2087
- "finalize",
2088
- "rollback",
2089
- LogLevel.WARNING,
2090
- {
2091
- "checkpoint": checkpoint_id,
2092
- "reason": rollback_reason,
2093
- },
2094
- )
2082
+ restored = False
2083
+ restore_error: str | None = None
2084
+ try:
2085
+ restored = bool(
2086
+ self.checkpoint_manager.restore_checkpoint(
2087
+ model, adapter, checkpoint_id
2088
+ )
2089
+ )
2090
+ except Exception as exc:
2091
+ restored = False
2092
+ restore_error = str(exc)
2093
+
2094
+ if restored:
2095
+ # Match test expectation: only include checkpoint and reason
2096
+ self._log_event(
2097
+ "finalize",
2098
+ "rollback",
2099
+ LogLevel.WARNING,
2100
+ {
2101
+ "checkpoint": checkpoint_id,
2102
+ "reason": rollback_reason,
2103
+ },
2104
+ )
2105
+ else:
2106
+ self._log_event(
2107
+ "finalize",
2108
+ "rollback_failed",
2109
+ LogLevel.CRITICAL,
2110
+ {
2111
+ "mode": "finalize",
2112
+ "checkpoint": checkpoint_id,
2113
+ "reason": rollback_reason,
2114
+ "error": restore_error or "restore_failed",
2115
+ },
2116
+ )
2095
2117
 
2096
2118
  # Store rollback metadata in report
2097
2119
  report.meta["rollback_reason"] = rollback_reason
2098
2120
  report.meta["rollback_checkpoint"] = checkpoint_id
2099
- report.meta["guard_recovered"] = True
2121
+ report.meta["guard_recovered"] = bool(restored)
2122
+ report.meta["rollback_failed"] = not bool(restored)
2123
+ if not restored:
2124
+ report.meta["rollback_error"] = restore_error or "restore_failed"
2100
2125
 
2101
2126
  else:
2102
2127
  # Match test expectation: log without additional data payload
@@ -7,6 +7,6 @@ from __future__ import annotations
7
7
  # matching entry to `CHANGELOG.md`.
8
8
 
9
9
  BENCH_GOLDEN_ID = "bench-golden-2025-12-13"
10
- BENCH_GOLDEN_SHA256 = "0d9ff3274d29dad16ad580b4a0cf37b4f89e4f7c2e4345ce3d30a39f146ff5a7"
10
+ BENCH_GOLDEN_SHA256 = "2627b8872cd6bfc37bda31fbc11b78ed814751cbf2a9ad1396e173f1f4e5383a"
11
11
 
12
12
  __all__ = ["BENCH_GOLDEN_ID", "BENCH_GOLDEN_SHA256"]
invarlock/eval/data.py CHANGED
@@ -950,6 +950,9 @@ class WikiText2Provider:
950
950
  if override_size is not None:
951
951
  batch_size = max(1, min(override_size, len(candidates)))
952
952
 
953
+ config = getattr(model, "config", None)
954
+ scorer_vocab_size = getattr(config, "vocab_size", None)
955
+
953
956
  input_batch: list[list[int]] = []
954
957
  attention_batch: list[list[int]] = []
955
958
  candidate_batch: list[dict[str, Any]] = []
@@ -970,6 +973,14 @@ class WikiText2Provider:
970
973
  attention_batch, dtype=torch.long, device=device
971
974
  )
972
975
 
976
+ # Guard against out-of-range token IDs when scoring with GPT-2.
977
+ # Some model tokenizers emit IDs beyond GPT-2 vocab, which can
978
+ # trigger device-side asserts in embedding/gather kernels.
979
+ if scorer_vocab_size and scorer_vocab_size > 0:
980
+ input_tensor = input_tensor.clamp(
981
+ min=0, max=scorer_vocab_size - 1
982
+ )
983
+
973
984
  outputs = model(input_tensor, attention_mask=attention_tensor)
974
985
  shift_logits = outputs.logits[:, :-1, :].contiguous()
975
986
  shift_labels = input_tensor[:, 1:].contiguous()
@@ -614,6 +614,13 @@ def _enforce_pairing_and_coverage(
614
614
  if not isinstance(stats, dict):
615
615
  raise ValueError("Missing dataset window stats for CI/Release enforcement.")
616
616
 
617
+ pairing_reason = stats.get("window_pairing_reason")
618
+ if pairing_reason is not None:
619
+ raise ValueError(
620
+ "CI/Release requires paired baseline evidence "
621
+ f"(window_pairing_reason={pairing_reason!r})."
622
+ )
623
+
617
624
  match_fraction = stats.get("window_match_fraction")
618
625
  overlap_fraction = stats.get("window_overlap_fraction")
619
626
  if not (
@@ -649,6 +656,12 @@ def _enforce_pairing_and_coverage(
649
656
  return None
650
657
  return int(round(val))
651
658
 
659
+ paired_windows = _coerce_count(stats.get("paired_windows"))
660
+ if paired_windows is None:
661
+ raise ValueError("CI/Release requires paired_windows metric.")
662
+ if paired_windows == 0:
663
+ raise ValueError("CI/Release requires paired_windows > 0.")
664
+
652
665
  actual_preview = _coerce_count(stats.get("actual_preview"))
653
666
  actual_final = _coerce_count(stats.get("actual_final"))
654
667
  if actual_preview is None or actual_final is None:
@@ -1188,6 +1201,20 @@ def make_certificate(
1188
1201
  _enforce_ratio_ci_alignment(ratio_ci_source, ratio_ci, logloss_delta_ci)
1189
1202
 
1190
1203
  paired_windows = _fallback_paired_windows(paired_windows, coverage_summary)
1204
+ # Prefer runner-reported paired window count when available (signal used for
1205
+ # CI/Release enforcement); fall back to evidence-based pairing or coverage
1206
+ # heuristics when the metric is missing.
1207
+ try:
1208
+ paired_windows_signal = (
1209
+ report.get("metrics", {}).get("paired_windows")
1210
+ if isinstance(report.get("metrics"), dict)
1211
+ else None
1212
+ )
1213
+ except Exception: # pragma: no cover
1214
+ paired_windows_signal = None
1215
+ paired_windows_signal_int = _coerce_int(paired_windows_signal)
1216
+ if paired_windows_signal_int is not None and paired_windows_signal_int >= 0:
1217
+ paired_windows = paired_windows_signal_int
1191
1218
 
1192
1219
  # Primary-metric stats for gating/summary (PM-only)
1193
1220
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invarlock
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: Edit‑agnostic robustness certificates for weight edits (InvarLock framework)
5
5
  Author-email: InvarLock Team <oss@invarlock.dev>
6
6
  Maintainer-email: InvarLock Maintainers <support@invarlock.dev>
@@ -112,7 +112,7 @@ they don’t, roll back safely.
112
112
  Technical: edit‑agnostic guard pipeline (invariants → spectral → RMT →
113
113
  variance) producing a machine‑readable Safety Certificate.
114
114
 
115
- > **Status:** 0.3.3 (pre‑1.0). Until 1.0, **minor** releases may be
115
+ > **Status:** 0.3.4 (pre‑1.0). Until 1.0, **minor** releases may be
116
116
  > breaking. See CLI help and the CHANGELOG for updates.
117
117
 
118
118
  [![CI](https://img.shields.io/github/actions/workflow/status/invarlock/invarlock/ci.yml?branch=main&logo=github&label=CI)](https://github.com/invarlock/invarlock/actions/workflows/ci.yml)
@@ -1,4 +1,4 @@
1
- invarlock/__init__.py,sha256=YSGaWWa-a0YHoSfUT1yUDKLTZHY5T4l-nSAUpBYHwkI,1268
1
+ invarlock/__init__.py,sha256=pTrxNy6MaRsu31Wvc1_V2TIENrSBVyVlk3AFDqggHxg,1268
2
2
  invarlock/__main__.py,sha256=ffhoKctw89j-henmQXThbHDIdlvK9fBfsy8LpjhOEXc,146
3
3
  invarlock/config.py,sha256=7BUOl7EW258YnsgRipjOx6lmWou5jNDzimREd35ewsQ,1725
4
4
  invarlock/model_profile.py,sha256=sFHpK-1Q-1DjiZTWMolQBG4Dw9feLJgk3GnaD3ixgd8,12809
@@ -11,17 +11,17 @@ invarlock/_data/runtime/profiles/ci_cpu.yaml,sha256=FE5vxeemAFWW7oRxPvp_8hIcCO8q
11
11
  invarlock/_data/runtime/profiles/release.yaml,sha256=xF0Qb0OTm904U6L3wK674JMTcDPegYvpKgwUB9pfq_w,482
12
12
  invarlock/adapters/__init__.py,sha256=Bwj8aKjhFxCzvcdxTIl-nG7IXyIE4L3Nd_fsIghbZxA,3418
13
13
  invarlock/adapters/_capabilities.py,sha256=FmzUR5BHsxWe92Z9W1As-G5_5wG1PvqF2sUpjZ2_CdY,1483
14
- invarlock/adapters/auto.py,sha256=XWENU5hi66AtMPlQDRuINk1SGW1Kym8Tot-2jXGFGcQ,7643
14
+ invarlock/adapters/auto.py,sha256=4F3I63PRW604v_NtaTY7aGvUJVjGSjejVKFff-X_1t4,7693
15
15
  invarlock/adapters/base.py,sha256=szSh1bECeDSDGQSr5oIWhs5RlI587gE4gzdt5cnOJ1s,16100
16
16
  invarlock/adapters/base_types.py,sha256=3IuHt63_RjGZqoTOdkMpfGPiZTGqcvXXDq1KU-8QemQ,1612
17
17
  invarlock/adapters/capabilities.py,sha256=oAK_zgCzAFmss8qAU2zgc8kcahtadGtbPTYR7StXiCo,15360
18
- invarlock/adapters/hf_bert.py,sha256=DkUXCile7ALlHVZvMkNLAl_YrhHpdmQmomzNyIAPBEo,35547
19
- invarlock/adapters/hf_gpt2.py,sha256=zuNapMDj4kdzlpGJY11tMOr9dh0V1C0qkOTRwi1xCnQ,14814
20
- invarlock/adapters/hf_llama.py,sha256=TbE9wII1GAQG7gbtiZCZ4S92rKoWRM7VH5vCnqEHc-4,19102
18
+ invarlock/adapters/hf_bert.py,sha256=KTEk2HRSPlH57bfdVjV6imn_-aYqXAlbnWfEFlRm8h4,35555
19
+ invarlock/adapters/hf_gpt2.py,sha256=TBn1cd_yZDSyVF5aJ-YNEwZzbVYI-m1c8U0CJl_74CM,14808
20
+ invarlock/adapters/hf_llama.py,sha256=P3T7m9j61EmHtzzhemIfM2I2e5nbl4RwXSdoaVsueGc,19141
21
21
  invarlock/adapters/hf_loading.py,sha256=6hdSFRz_JMtBzQfHcwvyDlIVP2y-KwLwhDorg73DZ6c,2742
22
- invarlock/adapters/hf_mixin.py,sha256=rhm0MbjzoHtEAl54tmW3T7tf1C_VxTtcPSdQ7mQ0CIU,18279
22
+ invarlock/adapters/hf_mixin.py,sha256=3YGoamAG6OCubsbJ9S583yYiQEy2fZOsBJeBiLZTEbk,22307
23
23
  invarlock/adapters/hf_onnx.py,sha256=kEqgQEEdGUeaXDF0EgaMHOQQMhc1xIvur0bQvdky-AY,4446
24
- invarlock/adapters/hf_t5.py,sha256=2SpDKPyERrrkTWXcDJDo0J5NNjFLePuC965e1zy2tus,4738
24
+ invarlock/adapters/hf_t5.py,sha256=sqHmi8io7oKlr7GrAk8tGo72zb-hmHGmXplCQSZgrA8,4774
25
25
  invarlock/adapters/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
26
26
  invarlock/assurance/__init__.py,sha256=SFDT2klaUaKZejulL5cfBTW29ehJxyW5srE-LuqP7z0,1388
27
27
  invarlock/calibration/__init__.py,sha256=M5lbkNQtLBuQjQJGeRzHovkpYI87fEWIm7a0b23jSp4,110
@@ -50,8 +50,8 @@ invarlock/cli/commands/explain_gates.py,sha256=CajKcu6rNi57yBsM_EUhi8rlmTl-ymT1k
50
50
  invarlock/cli/commands/export_html.py,sha256=oUGtt6EpKhjlywdtz_0FqYqcmA219H4eSjPuSMDShgY,3095
51
51
  invarlock/cli/commands/plugins.py,sha256=u5E6ThcWE5RUs2YKH-m6x7n-5K1jt2IudkF9kl9T5xg,53486
52
52
  invarlock/cli/commands/report.py,sha256=I-B11k6cLLNUIaNiKlsWrGgZ2cTQQJsarN3oNf9Dt84,12988
53
- invarlock/cli/commands/run.py,sha256=ywVpP0-s_nzaUHKli-T925drkNh3FiM6BAnPjEzFA5M,181818
54
- invarlock/cli/commands/verify.py,sha256=k7_H8vfjYUz7pzoEvRZe2xTSnTy4-HnbiAAsgBZXSOU,45883
53
+ invarlock/cli/commands/run.py,sha256=nf9Y_BcgxQyaCjwQmGUt8ZojDXBDPSeEk4Ut4_uRggM,200772
54
+ invarlock/cli/commands/verify.py,sha256=WAgIzV_CAZSkzRJIxVFV5llZAL3n3-biTMXLm6YL_qs,46408
55
55
  invarlock/core/__init__.py,sha256=4wb83Xv7NE5M1FgvaFUviiNtVSTVATiPH3gqavNmp2w,1490
56
56
  invarlock/core/abi.py,sha256=gmU1F7LDd2HTcF4kcznn8vgG4zj9UCHSqhBk9jyu05k,484
57
57
  invarlock/core/api.py,sha256=dg76wZaULo-T-_nZtq6rmDtWwSNLCSYXiQKVYySV6Fg,7545
@@ -64,7 +64,7 @@ invarlock/core/events.py,sha256=8XBAi-9A7ys7QJQwqlz8PVlfxF0TM_TvLqjcPtDwZm4,9428
64
64
  invarlock/core/exceptions.py,sha256=b4OszJ0Fd0Ezy8s99AzprS7lAkqdZYGXaSj9fYaln4E,2077
65
65
  invarlock/core/registry.py,sha256=a5hYHXl74MLnaPFbmpwJ-1OsJgY3SSe2Jisbzfr2rLM,19137
66
66
  invarlock/core/retry.py,sha256=KTVkrTnWs60jwATOZDHinERH56GnOGjsKR0lmohagEo,4503
67
- invarlock/core/runner.py,sha256=sz1cEF0_oke7KjHEPCyPuaZXQFW3uCO7rsbZOoFFFfA,92523
67
+ invarlock/core/runner.py,sha256=Tep0CDT75PbE8bpGapR8J00-MXZ-T0JB4C6tsqOMs68,93567
68
68
  invarlock/core/types.py,sha256=nVLMP4yqlxwhE1moQU7FWVeGJqTuud-cvTZiutdBGKk,3585
69
69
  invarlock/edits/__init__.py,sha256=5CJTX6oJTLj79Vmks2_8mXRGFQH0oyKaZOrBLTlRtV0,277
70
70
  invarlock/edits/_edit_utils.py,sha256=VGl32JAt7XsovxJbX1YvW9DJvtG-K2EKu-TjBFTQmDY,6329
@@ -75,9 +75,9 @@ invarlock/edits/quant_rtn.py,sha256=8rseGbF_-EFuL8MWCuzgGXYfzp9LsONhV72-OK56mlk,
75
75
  invarlock/edits/registry.py,sha256=MmUfJhhvc3WxB03wQkPxFMS6nkT7YcXFPQqhbksfOUc,4523
76
76
  invarlock/eval/__init__.py,sha256=GsKoszCysh3TT_UHiuJcqeoiXT7AUNZoqMuBmefnWtY,755
77
77
  invarlock/eval/bench.py,sha256=85tGywzAeJSVlZ1Qsebk8FwIGUYhq9g6TxOW5XnL5X8,56335
78
- invarlock/eval/bench_regression.py,sha256=-92lJbx2BP4utGIsIyahNQtouz56ghuuTEvj1yCaEoY,456
78
+ invarlock/eval/bench_regression.py,sha256=GDphmdcH7NI8P_afSkQAPMChEgW15J0gqTZ4Kj3kxgw,456
79
79
  invarlock/eval/bootstrap.py,sha256=CNn7W_MY716Wa_wn6cYRmbTYB9lT2X0yLOAIHCRw29k,1766
80
- invarlock/eval/data.py,sha256=nKx1XIkwmVKQyDP7aa40SFWk5GLVNxHI5gpmUWID3bE,75126
80
+ invarlock/eval/data.py,sha256=QVGOIiakm89jEy9-rVMlFJ6WYHq_keSrSNUOjRsVwCA,75736
81
81
  invarlock/eval/metrics.py,sha256=3ay3mqsFTa0V5a66gKdk7BOOJjKWXlub5lrTpcex9DQ,73946
82
82
  invarlock/eval/primary_metric.py,sha256=ay8gB7xGjWahXpJP-o4OcZFNRNZKcUIHjKmSeOUWJNk,28701
83
83
  invarlock/eval/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
@@ -117,7 +117,7 @@ invarlock/plugins/hf_bnb_adapter.py,sha256=g0ysWEi8dQzLtJy8iCszfTsYCOACuZMFYnTLM
117
117
  invarlock/plugins/hf_gptq_adapter.py,sha256=ysugAcnjLqF5sqpijSNiim1xUpRmoIgBrG053X3S2hE,3743
118
118
  invarlock/plugins/py.typed,sha256=LCPmZeE_vANVVJDNvuq9A07i7jg9Nxrq6f10UeuNfZc,37
119
119
  invarlock/reporting/__init__.py,sha256=A0541EqxcdTpslNbZEWIO4q-LCqzCQcadev2IBKEBbM,232
120
- invarlock/reporting/certificate.py,sha256=pq6ZPNMtzIhtiAjQ5tv8XUsRcCdxxqDAyJDlNGKIHWQ,145199
120
+ invarlock/reporting/certificate.py,sha256=yoMfALyXYGK6BBHJfsrCFDxb07o-9J0KQqui35RHkhM,146382
121
121
  invarlock/reporting/certificate_schema.py,sha256=ft58LrF4lfdXT08P0jjtiNvRPVs6TdgPU_cp9K4TMPw,8939
122
122
  invarlock/reporting/dataset_hashing.py,sha256=b9LM2rtOtD0-1gQq_oJ0yI6oLleempwKinQtLupajwI,8680
123
123
  invarlock/reporting/guards_analysis.py,sha256=7TTlF-OwgT7wGQ8zZ8oHHPuMdUMvKw7836WvO0J1GUc,42465
@@ -132,9 +132,9 @@ invarlock/reporting/utils.py,sha256=1aLYgSUR4XvgmhDvU9YK9ICd7W5sjft1qdsZC9JJSRY,
132
132
  invarlock/reporting/validate.py,sha256=oh7w8VrLM2EYZSJ2ySDNi7WKKmF1SgS81VkOrcHMzaE,22427
133
133
  invarlock/utils/__init__.py,sha256=DR2pBrgddLH2PW-6ninOE8CM7DNvlvgyYsCkckozbPU,4276
134
134
  invarlock/utils/digest.py,sha256=sfnqGFRiRf7l950MjSIrWO1XbUfXlcEfNLeWFbBUr8I,1290
135
- invarlock-0.3.3.dist-info/licenses/LICENSE,sha256=uFddaXYY02nEFdPpS7bam_bnm0st41BibzD0jHULPXw,10413
136
- invarlock-0.3.3.dist-info/METADATA,sha256=TtfgKz4riaRxKJcgSBmwukAbdtTkHYhj9Oej84nfs1I,21845
137
- invarlock-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
138
- invarlock-0.3.3.dist-info/entry_points.txt,sha256=i0e4ZzmJNMBGG-69lbgP-muEcn1je2TUIWwl9SJERm0,670
139
- invarlock-0.3.3.dist-info/top_level.txt,sha256=GXfftc_YDHHcQC2vQgYbZ5cTO82YuWY3HusHMT3DuKs,10
140
- invarlock-0.3.3.dist-info/RECORD,,
135
+ invarlock-0.3.4.dist-info/licenses/LICENSE,sha256=uFddaXYY02nEFdPpS7bam_bnm0st41BibzD0jHULPXw,10413
136
+ invarlock-0.3.4.dist-info/METADATA,sha256=RycW7PQJeWJgfX-e3KbqUi_zjXxDPzLkchaTVoS_SCA,21845
137
+ invarlock-0.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
138
+ invarlock-0.3.4.dist-info/entry_points.txt,sha256=i0e4ZzmJNMBGG-69lbgP-muEcn1je2TUIWwl9SJERm0,670
139
+ invarlock-0.3.4.dist-info/top_level.txt,sha256=GXfftc_YDHHcQC2vQgYbZ5cTO82YuWY3HusHMT3DuKs,10
140
+ invarlock-0.3.4.dist-info/RECORD,,