vitnify 0.4.2__tar.gz → 0.4.4__tar.gz

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 (42) hide show
  1. {vitnify-0.4.2/src/vitnify.egg-info → vitnify-0.4.4}/PKG-INFO +4 -2
  2. {vitnify-0.4.2 → vitnify-0.4.4}/README.md +3 -1
  3. {vitnify-0.4.2 → vitnify-0.4.4}/pyproject.toml +1 -1
  4. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/viewer.py +28 -6
  5. {vitnify-0.4.2 → vitnify-0.4.4/src/vitnify.egg-info}/PKG-INFO +4 -2
  6. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_viewer.py +27 -1
  7. {vitnify-0.4.2 → vitnify-0.4.4}/LICENSE +0 -0
  8. {vitnify-0.4.2 → vitnify-0.4.4}/NOTICE +0 -0
  9. {vitnify-0.4.2 → vitnify-0.4.4}/setup.cfg +0 -0
  10. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/__init__.py +0 -0
  11. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/_vendor/__init__.py +0 -0
  12. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/_vendor/pck/__init__.py +0 -0
  13. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/_vendor/pck/cas.py +0 -0
  14. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/capability.py +0 -0
  15. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/certificate.py +0 -0
  16. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/deterministic_llm.py +0 -0
  17. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/engine.py +0 -0
  18. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/events.py +0 -0
  19. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/langgraph_adapter.py +0 -0
  20. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/mcp_adapter.py +0 -0
  21. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/react.py +0 -0
  22. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/redact.py +0 -0
  23. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/replayer.py +0 -0
  24. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify.egg-info/SOURCES.txt +0 -0
  25. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify.egg-info/dependency_links.txt +0 -0
  26. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify.egg-info/requires.txt +0 -0
  27. {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify.egg-info/top_level.txt +0 -0
  28. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_adapters.py +0 -0
  29. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_attack_matrix.py +0 -0
  30. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_authority_program.py +0 -0
  31. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_capability.py +0 -0
  32. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_certificate.py +0 -0
  33. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_ed25519.py +0 -0
  34. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_engine_seam.py +0 -0
  35. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_events.py +0 -0
  36. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_l2_recompute.py +0 -0
  37. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_level2_verify.py +0 -0
  38. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_receipt_v2.py +0 -0
  39. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_redact.py +0 -0
  40. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_safe_defaults.py +0 -0
  41. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_verification.py +0 -0
  42. {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_verify_hardening.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vitnify
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Execution receipts for AI agents: contain, deterministically reconstruct, and cryptographically certify a run.
5
5
  Author: vitnify
6
6
  License: Apache-2.0
@@ -91,7 +91,9 @@ See the [receipt format spec](https://github.com/vitnify/vitnify-receipt-spec/bl
91
91
  **Two verification levels — and when to use each.** *Level 1 (integrity)* is offline,
92
92
  instant, and needs no model — recompute the Merkle root and check the signature; this is
93
93
  the default for every receipt, and it's what proves containment and tamper-evidence.
94
- *Level 2 (recompute)* additionally re-runs the model to reproduce the committed logits.
94
+ *Level 2 (recompute)* additionally re-runs the model to reproduce the committed output
95
+ tokens (per-op / activation records — a stronger commitment — are an available mode the
96
+ shipped binary does not emit).
95
97
  It is the **dispute path** — run on a contested subset when someone challenges a specific
96
98
  decision, **not** on every receipt inline. It is deliberately slow: the pinned-order
97
99
  deterministic engine trades throughput for bit-exactness, roughly two orders of magnitude
@@ -65,7 +65,9 @@ See the [receipt format spec](https://github.com/vitnify/vitnify-receipt-spec/bl
65
65
  **Two verification levels — and when to use each.** *Level 1 (integrity)* is offline,
66
66
  instant, and needs no model — recompute the Merkle root and check the signature; this is
67
67
  the default for every receipt, and it's what proves containment and tamper-evidence.
68
- *Level 2 (recompute)* additionally re-runs the model to reproduce the committed logits.
68
+ *Level 2 (recompute)* additionally re-runs the model to reproduce the committed output
69
+ tokens (per-op / activation records — a stronger commitment — are an available mode the
70
+ shipped binary does not emit).
69
71
  It is the **dispute path** — run on a contested subset when someone challenges a specific
70
72
  decision, **not** on every receipt inline. It is deliberately slow: the pinned-order
71
73
  deterministic engine trades throughput for bit-exactness, roughly two orders of magnitude
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vitnify"
7
- version = "0.4.2"
7
+ version = "0.4.4"
8
8
  description = "Execution receipts for AI agents: contain, deterministically reconstruct, and cryptographically certify a run."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -119,7 +119,7 @@ def render(run: dict) -> str:
119
119
  # UNESTABLISHED (no trust root) -- shown as such, NOT as "failed".
120
120
  integrity_ok = bool(v.get("integrity_ok"))
121
121
  authority_ok = v.get("authority_ok") # True / False / None
122
- replay_ok = bool(v.get("replay_identical"))
122
+ replay = v.get("replay_identical") # True / False / None — see the three-state badge
123
123
  # Containment is trustworthy only on a verified (untampered) log -- that is INTEGRITY,
124
124
  # not authority: a stranger with no trust root can still see the run was contained.
125
125
  containment_proven = integrity_ok and bool(tool_events) and all_gated
@@ -144,15 +144,24 @@ def render(run: dict) -> str:
144
144
  else:
145
145
  containment_badge = badge(True, "Containment enforced", "") # every call gated; none needed blocking
146
146
 
147
- badges = (integrity_badge + authority_badge + containment_badge
148
- + badge(replay_ok, "Replay bit-identical", "Replay diverged"))
147
+ # Replay is ALSO three-state: not-run (absent) must not read as diverged. L2 recompute
148
+ # is the DISPUTE path, so the common receipt carries no replay data -- rendering that as
149
+ # a red "diverged" asserts a failure that never happened (the same shape as authority).
150
+ if replay is True:
151
+ replay_badge = badge(True, "Replay bit-identical", "")
152
+ elif replay is False:
153
+ replay_badge = badge(False, "", "Replay diverged")
154
+ else:
155
+ replay_badge = neutral_badge("Replay not run — L1 verdict only")
156
+
157
+ badges = integrity_badge + authority_badge + containment_badge + replay_badge
149
158
 
150
159
  # Headline asserts ONLY what the evidence supports. Integrity is the base claim (anyone
151
160
  # can verify it offline); authority is claimed ONLY when a trust root confirmed it --
152
161
  # an unestablished authority is never dressed up as verified.
153
162
  claims = (["integrity-verified"] if integrity_ok else []) \
154
163
  + (["contained"] if containment_proven else []) \
155
- + (["replayed bit-for-bit"] if replay_ok else []) \
164
+ + (["replayed bit-for-bit"] if replay is True else []) \
156
165
  + (["signed by an approved runtime"] if authority_ok is True else [])
157
166
  if not claims:
158
167
  headline = "Recorded agent run."
@@ -171,9 +180,22 @@ def render(run: dict) -> str:
171
180
  ("event-log root", cert["event_root"]),
172
181
  ("chain head", cert["head_hash"]),
173
182
  ("certificate id", cert.get("digest", "—")),
174
- ("signature", cert.get("sig", "—")),
183
+ (f"signature ({_esc(cert['sig_alg'])})" if cert.get("sig_alg") else "signature",
184
+ cert.get("sig", "—")),
175
185
  ])
176
186
 
187
+ # Footer is DERIVED, never a hardcoded claim. State only what the page proves, and the
188
+ # ACTUAL signature algorithm from the receipt (not a fixed "HMAC"). No batch-invariance
189
+ # claim: that determinism property is NOT closed by the engine -- it is precisely why
190
+ # level 2 is a single-request path, so asserting it here would be false.
191
+ sig_alg = cert.get("sig_alg")
192
+ sig_note = f"Signed with {_esc(sig_alg)}. " if sig_alg else ""
193
+ footer_text = (sig_note +
194
+ "Verified offline from the receipt alone — no model, no network, no secret. "
195
+ "Level-2 recomputation (re-run the model to reproduce the committed output tokens) is the "
196
+ "dispute path for a contested run, not required for the integrity, containment, and "
197
+ "authority verdicts above.")
198
+
177
199
  return f"""<title>Agent Replay Certificate</title>
178
200
  <style>
179
201
  :root {{
@@ -277,7 +299,7 @@ footer{{color:var(--ink-dim);font-size:12px;margin-top:26px;text-align:center;}}
277
299
  </div>
278
300
  </section>
279
301
 
280
- <footer>Recorded under production batch load, replayed alone &mdash; bit-identical only because inference is batch-invariant. HMAC signing shown; ed25519/TPM in production.</footer>
302
+ <footer>{_esc(footer_text)}</footer>
281
303
  </div>
282
304
 
283
305
  <script>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vitnify
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Execution receipts for AI agents: contain, deterministically reconstruct, and cryptographically certify a run.
5
5
  Author: vitnify
6
6
  License: Apache-2.0
@@ -91,7 +91,9 @@ See the [receipt format spec](https://github.com/vitnify/vitnify-receipt-spec/bl
91
91
  **Two verification levels — and when to use each.** *Level 1 (integrity)* is offline,
92
92
  instant, and needs no model — recompute the Merkle root and check the signature; this is
93
93
  the default for every receipt, and it's what proves containment and tamper-evidence.
94
- *Level 2 (recompute)* additionally re-runs the model to reproduce the committed logits.
94
+ *Level 2 (recompute)* additionally re-runs the model to reproduce the committed output
95
+ tokens (per-op / activation records — a stronger commitment — are an available mode the
96
+ shipped binary does not emit).
95
97
  It is the **dispute path** — run on a contested subset when someone challenges a specific
96
98
  decision, **not** on every receipt inline. It is deliberately slow: the pinned-order
97
99
  deterministic engine trades throughput for bit-exactness, roughly two orders of magnitude
@@ -37,7 +37,7 @@ def _build(decisions, *, cert_ok=True, replay=True, authority=None):
37
37
  "events": [asdict(e) for e in log.events],
38
38
  "certificate": {"program_hash": cert.program_hash, "capabilities": cert.capabilities,
39
39
  "event_root": cert.event_root, "head_hash": cert.head_hash,
40
- "digest": cert.digest(), "sig": cert.sig},
40
+ "digest": cert.digest(), "sig": cert.sig, "sig_alg": cert.sig_alg},
41
41
  "verdict": {"replay_identical": replay, "integrity_ok": cert_ok, "authority_ok": authority},
42
42
  }
43
43
  return run, log
@@ -98,6 +98,32 @@ def test_authority_badge_is_three_state():
98
98
  assert "signed by an approved runtime" not in _headline(render(_build(["allow"], authority=None)[0]))
99
99
 
100
100
 
101
+ def test_replay_badge_is_three_state():
102
+ """not-run (absent) must read neutral, NOT a red 'diverged' -- the same false-negative
103
+ the authority split removed, and now the COMMON case since L2 is the dispute path."""
104
+ run, _ = _build(["allow"], replay=None) # no replay data (L1-only receipt)
105
+ b = _badges(render(run))
106
+ assert ("neutral", "Replay not run — L1 verdict only") in b
107
+ assert not any(t == "Replay diverged" for _, t in b)
108
+ assert "replayed bit-for-bit" not in _headline(render(run))
109
+ # ran & identical -> good; ran & diverged -> bad (distinct from not-run)
110
+ assert ("good", "Replay bit-identical") in _badges(render(_build(["allow"], replay=True)[0]))
111
+ assert ("bad", "Replay diverged") in _badges(render(_build(["allow"], replay=False)[0]))
112
+
113
+
114
+ def test_footer_makes_no_unbacked_claims():
115
+ """The footer must not assert batch-invariance (not an engine property) or a fixed
116
+ HMAC (the demo signs ed25519). It is derived from the run, not hardcoded marketing."""
117
+ html = render(_build(["allow"])[0])
118
+ assert "batch-invariant" not in html and "batch invariant" not in html
119
+ assert "HMAC signing" not in html
120
+ assert "ed25519" in html # the real signature algorithm, shown
121
+ # L2 reproduces the committed OUTPUT TOKENS, not logits (the shipped binary emits
122
+ # output_tokens + hash, n_ops=0; logits are a stronger, non-default mode). Never overclaim.
123
+ assert "committed logits" not in html
124
+ assert "committed output tokens" in html
125
+
126
+
101
127
  def test_cert_failed_drops_all_derived_claims():
102
128
  # If the certificate does not verify, the event log is UNVERIFIED -- it may be the
103
129
  # very forgery the certificate exists to catch -- so the page must not claim
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes