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.
- {vitnify-0.4.2/src/vitnify.egg-info → vitnify-0.4.4}/PKG-INFO +4 -2
- {vitnify-0.4.2 → vitnify-0.4.4}/README.md +3 -1
- {vitnify-0.4.2 → vitnify-0.4.4}/pyproject.toml +1 -1
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/viewer.py +28 -6
- {vitnify-0.4.2 → vitnify-0.4.4/src/vitnify.egg-info}/PKG-INFO +4 -2
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_viewer.py +27 -1
- {vitnify-0.4.2 → vitnify-0.4.4}/LICENSE +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/NOTICE +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/setup.cfg +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/__init__.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/_vendor/__init__.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/_vendor/pck/__init__.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/_vendor/pck/cas.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/capability.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/certificate.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/deterministic_llm.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/engine.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/events.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/langgraph_adapter.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/mcp_adapter.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/react.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/redact.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify/replayer.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify.egg-info/SOURCES.txt +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify.egg-info/dependency_links.txt +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify.egg-info/requires.txt +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/src/vitnify.egg-info/top_level.txt +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_adapters.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_attack_matrix.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_authority_program.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_capability.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_certificate.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_ed25519.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_engine_seam.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_events.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_l2_recompute.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_level2_verify.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_receipt_v2.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_redact.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_safe_defaults.py +0 -0
- {vitnify-0.4.2 → vitnify-0.4.4}/tests/test_verification.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
|
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"
|
|
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>
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|