vouch-protocol 2.0.1__tar.gz → 2.0.2__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.
- {vouch_protocol-2.0.1/vouch_protocol.egg-info → vouch_protocol-2.0.2}/PKG-INFO +2 -2
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/README.md +1 -1
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/pyproject.toml +1 -1
- vouch_protocol-2.0.2/tests/test_robot_consent.py +200 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/__init__.py +1 -1
- vouch_protocol-2.0.2/vouch/integrations/mlflow.py +101 -0
- vouch_protocol-2.0.2/vouch/integrations/safetensors.py +126 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/__init__.py +20 -0
- vouch_protocol-2.0.2/vouch/robotics/consent.py +309 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2/vouch_protocol.egg-info}/PKG-INFO +2 -2
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch_protocol.egg-info/SOURCES.txt +4 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/LICENSE +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/MANIFEST.in +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/setup.cfg +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_accountability.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_attribution.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_audio_bridge.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_audit_trail.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_auditor.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_autosign.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_behavioral_attestation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_bridge.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_budget.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_c2pa_formats.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_cache.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_canary.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_caveats.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_cli_init.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_conformance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_credential_gate.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_data_integrity.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_deliberation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_dx_sugar.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_fastapi_credential_gate.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_fleet.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_git_workflow.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_grade.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_hasura_integration.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_heartbeat.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_hybrid.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_hybrid_interop.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_jcs.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_jcs_interop.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_liveness_conformance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_media_badge.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_media_native.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_merkle.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_multikey.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_nonce.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_proof_of_integration.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_provenance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_quorum.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_reasoning.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_receipts.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_recovery.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_reputation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_reputation_aggregate.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_reputation_disputes.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_reputation_ledger.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_reputation_policy.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_reputation_portability.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_revocation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_access.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_conformance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_custody.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_embodiment.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_fusion.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_handshake_blackbox_passport.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_identity.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_lease_quorum.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_lifecycle.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_liveness_revocation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_perception.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_pq.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_provenance_capability.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_safety_record.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_robot_wear.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_scan.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_secure_key_custody.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_security_hardening.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_shield_guard.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_signer_vc.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_status_list.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_threshold.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_tool_registry.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_transparency.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_transport.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_transport_http_rendezvous.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_transport_rendezvous.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_transport_udna_sdk.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_trifecta.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_trust_check.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_trust_entropy.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_validator_server.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_vc.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/tests/test_verifier_vc.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/accountability.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/agent.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/async_verifier.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/attribution.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/attribution_cli.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/audio.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/audit_trail.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/auditor.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/autosign.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/behavioral_attestation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/bridge/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/bridge/audio_routes.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/bridge/config.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/bridge/server.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/budget.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/cache.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/canary.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/caveats.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/cli.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/config.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/conformance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/credential.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/data_integrity.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/data_integrity_hybrid.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/deliberation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/did_web.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/fleet.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/gate.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/grade.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/heartbeat.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/_common.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/a2a.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/adk.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/amnesia.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/autogen/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/autogpt/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/autogpt/commands.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/claude-code/settings.hooks.json +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/crewai/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/crewai/tool.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/fastapi.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/google.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/goose.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/hasura/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/hasura/webhook.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/langchain/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/langchain/tool.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/langgraph.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/mcp/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/mcp/server.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/n8n.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/streamlit/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/streamlit/seal.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/integrations/vertex_ai/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/jcs.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/keys.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/keystore.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/kms.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/liveness_conformance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/mcp_guard.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/media/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/media/badge.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/media/c2pa.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/media/native.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/merkle.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/metrics.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/multikey.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/nonce.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/onboard.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/proof_of_integration.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/provenance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/quorum.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/ratelimit.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/reasoning.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/receipts.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/recovery.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/registry.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/reputation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/reputation_aggregate.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/reputation_disputes.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/reputation_ledger.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/reputation_policy.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/reputation_portability.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/revocation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/_signing.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/access.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/blackbox.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/capability.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/conformance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/custody.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/embodiment.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/fusion.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/handshake.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/identity.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/lease.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/lifecycle.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/liveness.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/passport.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/perception.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/physical_quorum.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/pq.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/provenance.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/revocation.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/safety_record.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/robotics/wear.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/scan/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/scan/detector.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/scan/patterns.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/seal.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/shield/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/shield/demo.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/shield/flight_recorder.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/shield/permissions.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/shield/shield.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/shield/trust_registry.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/signer.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/ssrf.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/status_list.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/status_list_fetcher.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/threshold.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/tool_registry.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transparency.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/__init__.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/base.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/did_key.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/envelope.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/http_rendezvous.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/http_transport.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/manager.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/rendezvous.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/transport/udna.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/trifecta.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/trust_check.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/trust_entropy.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/validator_server.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/vc.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/verification.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch/verifier.py +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch_protocol.egg-info/dependency_links.txt +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch_protocol.egg-info/entry_points.txt +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch_protocol.egg-info/requires.txt +0 -0
- {vouch_protocol-2.0.1 → vouch_protocol-2.0.2}/vouch_protocol.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vouch-protocol
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: The Identity & Reputation Standard for AI Agents
|
|
5
5
|
Author-email: Vouch Protocol Contributors <hello@vouch-protocol.com>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -394,7 +394,7 @@ The standalone **`vouch-mcp`** package above ships alongside these in v1.6.2.
|
|
|
394
394
|
Python, TypeScript, and Go are the full reference implementations. A Rust core with idiomatic Swift, JVM (Java and Kotlin), .NET, and C wrappers shares one codebase, so every language produces byte-identical output, verified against shared test vectors. A WebAssembly build is included for the browser and the edge. See the table further down for status per language.
|
|
395
395
|
|
|
396
396
|
### Robots and embodied agents
|
|
397
|
-
A robot is an agent with a body, so identity and accountability matter even more once it can cause physical harm. The `vouch.robotics` module ships six capabilities on the same `eddsa-jcs-2022` credentials as the rest of Vouch: hardware-rooted identity (bound to a TPM or secure element, so it cannot be cloned to other hardware), model and config provenance (re-signable on every OTA update), physical capability scope (force, speed, a tighter cap near humans, allowed zones, and shift windows, checked before each actuation, with narrow-only delegation), a robot-to-robot trust handshake, an encrypted tamper-evident black box with a verifiable kill switch, and a scannable offline passport. All six are implemented in Python, TypeScript, Go, and the Rust core, which flows to the Swift, Kotlin/JVM, .NET, C/C++, and WebAssembly wrappers, so a robotics credential signed in one language verifies in every other.
|
|
397
|
+
A robot is an agent with a body, so identity and accountability matter even more once it can cause physical harm. The `vouch.robotics` module ships six capabilities on the same `eddsa-jcs-2022` credentials as the rest of Vouch: hardware-rooted identity (bound to a TPM or secure element, so it cannot be cloned to other hardware), model and config provenance (re-signable on every OTA update), physical capability scope (force, speed, a tighter cap near humans, allowed zones, and shift windows, checked before each actuation, with narrow-only delegation), a robot-to-robot trust handshake, an encrypted tamper-evident black box with a verifiable kill switch, and a scannable offline passport. All six are implemented in Python, TypeScript, Go, and the Rust core, which flows to the Swift, Kotlin/JVM, .NET, C/C++, and WebAssembly wrappers, so a robotics credential signed in one language verifies in every other. Fifteen further capabilities round out the set: a living-trust heartbeat (a signed per-interval motion summary whose trust decays unless it is renewed in-envelope), two-level credential revocation (per-credential status lists and whole-DID kill), an accountable safety record (a tamper-evident incident ledger summarized into a portable signed record), signed perception provenance (each captured sensor frame's hash bound to the robot's key and hash-linked, so a robot can prove what its sensors saw and a substituted frame is detectable), an offline delegation lease (a short-lived, scope-bounded grant a disconnected robot verifies and acts on with no network call, nesting across vendors), a physical quorum (a cryptographic two-person rule requiring M of N attested approvers for a high-consequence action), lifecycle credentials (ownership transfer that chains into a verifiable chain of custody, key rotation that forms a key history, and a signed decommission that retires the robot), a regulatory conformance profile (a machine-checkable mapping from a robot's credentials to the clauses of ISO 10218 and 15066, the EU Machinery Regulation, the EU AI Act, and UL 3300, with a deterministic checker and a signed conformance attestation), post-quantum signing by default (robot credentials sign with the hybrid classical-plus-ML-DSA-44 cryptosuite, with backward-compatible verification, so a robot stays unforgeable across its decade-long life), and cross-embodiment identity continuity (an agent identity that moves between robot bodies, with an embodiment credential re-binding to each body's hardware root and a continuity chain that proves the same accountable agent persisted, plus a fork check), a physical custody handoff chain (a signed record of who accepted custody of a task or object at each hop across human and robot actors, so an incident traces to the exact hop and an attested condition localizes damage to the holder responsible), bounded infrastructure access (an operator-signed grant naming a resource, its permitted operations, an optional zone, and a time window, which a robot pairs with a signed request the resource authorizes offline, so a robot can open a door or dock at a charger with a tamper-evident, attributable, shrink-only record), fused-sensor provenance (a signed attestation binding a robot's fused world model to the exact set of input frame hashes and the fusion method that produced it, so a manipulated fusion result or a dropped input is detectable and every fused input traces to a frame the robot recorded), wear and degradation attestation (a robot signs its own wear level bound to its identity and hash-linked over time, and a deterministic rule narrows its physical capability scope as it degrades, so a worn robot operates inside a tighter, verifiable envelope than the static limit it shipped with), and bystander-consent evidence (a robot binds the basis for capturing people, an explicit token, posted notice, a legitimate interest, or a redaction it applied, to the capture itself by its hash, holding only hashes and never anyone's identifying data, and a bystander can sign consent bound to one capture so it cannot be replayed to another), implemented across the same languages and pinned by the shared interop vector. See [docs/robotics.md](docs/robotics.md) and the defensive disclosures PAD-064 through PAD-070, PAD-076 through PAD-084, and PAD-087 through PAD-094.
|
|
398
398
|
|
|
399
399
|
### Inside your AI tools
|
|
400
400
|
- **Claude Skill**, **OpenAI Custom GPT**, and **Gemini Gem** packages that teach your AI assistant how to add Vouch to your code, running on your own AI subscription.
|
|
@@ -112,7 +112,7 @@ The standalone **`vouch-mcp`** package above ships alongside these in v1.6.2.
|
|
|
112
112
|
Python, TypeScript, and Go are the full reference implementations. A Rust core with idiomatic Swift, JVM (Java and Kotlin), .NET, and C wrappers shares one codebase, so every language produces byte-identical output, verified against shared test vectors. A WebAssembly build is included for the browser and the edge. See the table further down for status per language.
|
|
113
113
|
|
|
114
114
|
### Robots and embodied agents
|
|
115
|
-
A robot is an agent with a body, so identity and accountability matter even more once it can cause physical harm. The `vouch.robotics` module ships six capabilities on the same `eddsa-jcs-2022` credentials as the rest of Vouch: hardware-rooted identity (bound to a TPM or secure element, so it cannot be cloned to other hardware), model and config provenance (re-signable on every OTA update), physical capability scope (force, speed, a tighter cap near humans, allowed zones, and shift windows, checked before each actuation, with narrow-only delegation), a robot-to-robot trust handshake, an encrypted tamper-evident black box with a verifiable kill switch, and a scannable offline passport. All six are implemented in Python, TypeScript, Go, and the Rust core, which flows to the Swift, Kotlin/JVM, .NET, C/C++, and WebAssembly wrappers, so a robotics credential signed in one language verifies in every other.
|
|
115
|
+
A robot is an agent with a body, so identity and accountability matter even more once it can cause physical harm. The `vouch.robotics` module ships six capabilities on the same `eddsa-jcs-2022` credentials as the rest of Vouch: hardware-rooted identity (bound to a TPM or secure element, so it cannot be cloned to other hardware), model and config provenance (re-signable on every OTA update), physical capability scope (force, speed, a tighter cap near humans, allowed zones, and shift windows, checked before each actuation, with narrow-only delegation), a robot-to-robot trust handshake, an encrypted tamper-evident black box with a verifiable kill switch, and a scannable offline passport. All six are implemented in Python, TypeScript, Go, and the Rust core, which flows to the Swift, Kotlin/JVM, .NET, C/C++, and WebAssembly wrappers, so a robotics credential signed in one language verifies in every other. Fifteen further capabilities round out the set: a living-trust heartbeat (a signed per-interval motion summary whose trust decays unless it is renewed in-envelope), two-level credential revocation (per-credential status lists and whole-DID kill), an accountable safety record (a tamper-evident incident ledger summarized into a portable signed record), signed perception provenance (each captured sensor frame's hash bound to the robot's key and hash-linked, so a robot can prove what its sensors saw and a substituted frame is detectable), an offline delegation lease (a short-lived, scope-bounded grant a disconnected robot verifies and acts on with no network call, nesting across vendors), a physical quorum (a cryptographic two-person rule requiring M of N attested approvers for a high-consequence action), lifecycle credentials (ownership transfer that chains into a verifiable chain of custody, key rotation that forms a key history, and a signed decommission that retires the robot), a regulatory conformance profile (a machine-checkable mapping from a robot's credentials to the clauses of ISO 10218 and 15066, the EU Machinery Regulation, the EU AI Act, and UL 3300, with a deterministic checker and a signed conformance attestation), post-quantum signing by default (robot credentials sign with the hybrid classical-plus-ML-DSA-44 cryptosuite, with backward-compatible verification, so a robot stays unforgeable across its decade-long life), and cross-embodiment identity continuity (an agent identity that moves between robot bodies, with an embodiment credential re-binding to each body's hardware root and a continuity chain that proves the same accountable agent persisted, plus a fork check), a physical custody handoff chain (a signed record of who accepted custody of a task or object at each hop across human and robot actors, so an incident traces to the exact hop and an attested condition localizes damage to the holder responsible), bounded infrastructure access (an operator-signed grant naming a resource, its permitted operations, an optional zone, and a time window, which a robot pairs with a signed request the resource authorizes offline, so a robot can open a door or dock at a charger with a tamper-evident, attributable, shrink-only record), fused-sensor provenance (a signed attestation binding a robot's fused world model to the exact set of input frame hashes and the fusion method that produced it, so a manipulated fusion result or a dropped input is detectable and every fused input traces to a frame the robot recorded), wear and degradation attestation (a robot signs its own wear level bound to its identity and hash-linked over time, and a deterministic rule narrows its physical capability scope as it degrades, so a worn robot operates inside a tighter, verifiable envelope than the static limit it shipped with), and bystander-consent evidence (a robot binds the basis for capturing people, an explicit token, posted notice, a legitimate interest, or a redaction it applied, to the capture itself by its hash, holding only hashes and never anyone's identifying data, and a bystander can sign consent bound to one capture so it cannot be replayed to another), implemented across the same languages and pinned by the shared interop vector. See [docs/robotics.md](docs/robotics.md) and the defensive disclosures PAD-064 through PAD-070, PAD-076 through PAD-084, and PAD-087 through PAD-094.
|
|
116
116
|
|
|
117
117
|
### Inside your AI tools
|
|
118
118
|
- **Claude Skill**, **OpenAI Custom GPT**, and **Gemini Gem** packages that teach your AI assistant how to add Vouch to your code, running on your own AI subscription.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Tests for bystander-consent evidence."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
|
|
6
|
+
from vouch import Signer, generate_identity
|
|
7
|
+
from vouch.robotics import (
|
|
8
|
+
build_consent_evidence,
|
|
9
|
+
build_consent_token,
|
|
10
|
+
hash_capture,
|
|
11
|
+
verify_consent_evidence,
|
|
12
|
+
verify_consent_token,
|
|
13
|
+
)
|
|
14
|
+
from vouch.robotics.identity import RoboticsError
|
|
15
|
+
|
|
16
|
+
T0 = datetime(2026, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
|
|
17
|
+
IN_WINDOW = datetime(2026, 1, 1, 0, 5, 0, tzinfo=timezone.utc)
|
|
18
|
+
AFTER = datetime(2026, 1, 1, 2, 0, 0, tzinfo=timezone.utc)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _party(domain):
|
|
22
|
+
kp = generate_identity(domain=domain)
|
|
23
|
+
return kp, Signer(private_key=kp.private_key_jwk, did=kp.did)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestConsentToken(unittest.TestCase):
|
|
27
|
+
def setUp(self):
|
|
28
|
+
self.by_kp, self.bystander = _party("person-1.example.com")
|
|
29
|
+
self.robot_kp, self.robot = _party("robot-a.example.com")
|
|
30
|
+
self.capture_hash = hash_capture(b"frame-0")
|
|
31
|
+
|
|
32
|
+
def _token(self):
|
|
33
|
+
return build_consent_token(
|
|
34
|
+
self.bystander,
|
|
35
|
+
bystander_did=self.by_kp.did,
|
|
36
|
+
capture_hash=self.capture_hash,
|
|
37
|
+
robot_did=self.robot_kp.did,
|
|
38
|
+
valid_seconds=3600,
|
|
39
|
+
granted_at=T0,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def test_token_verifies_for_its_capture(self):
|
|
43
|
+
ok, subject = verify_consent_token(
|
|
44
|
+
self._token(),
|
|
45
|
+
self.by_kp.public_key_jwk,
|
|
46
|
+
capture_hash=self.capture_hash,
|
|
47
|
+
robot_did=self.robot_kp.did,
|
|
48
|
+
now=IN_WINDOW,
|
|
49
|
+
)
|
|
50
|
+
self.assertTrue(ok)
|
|
51
|
+
self.assertEqual(subject["robotDid"], self.robot_kp.did)
|
|
52
|
+
|
|
53
|
+
def test_token_rejected_for_other_capture(self):
|
|
54
|
+
ok, _ = verify_consent_token(
|
|
55
|
+
self._token(),
|
|
56
|
+
self.by_kp.public_key_jwk,
|
|
57
|
+
capture_hash=hash_capture(b"different-frame"),
|
|
58
|
+
robot_did=self.robot_kp.did,
|
|
59
|
+
now=IN_WINDOW,
|
|
60
|
+
)
|
|
61
|
+
self.assertFalse(ok)
|
|
62
|
+
|
|
63
|
+
def test_token_rejected_for_other_robot(self):
|
|
64
|
+
ok, _ = verify_consent_token(
|
|
65
|
+
self._token(),
|
|
66
|
+
self.by_kp.public_key_jwk,
|
|
67
|
+
capture_hash=self.capture_hash,
|
|
68
|
+
robot_did="did:web:robot-z.example.com",
|
|
69
|
+
now=IN_WINDOW,
|
|
70
|
+
)
|
|
71
|
+
self.assertFalse(ok)
|
|
72
|
+
|
|
73
|
+
def test_token_out_of_window_rejected(self):
|
|
74
|
+
ok, _ = verify_consent_token(
|
|
75
|
+
self._token(),
|
|
76
|
+
self.by_kp.public_key_jwk,
|
|
77
|
+
capture_hash=self.capture_hash,
|
|
78
|
+
robot_did=self.robot_kp.did,
|
|
79
|
+
now=AFTER,
|
|
80
|
+
)
|
|
81
|
+
self.assertFalse(ok)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestConsentEvidence(unittest.TestCase):
|
|
85
|
+
def setUp(self):
|
|
86
|
+
self.by_kp, self.bystander = _party("person-1.example.com")
|
|
87
|
+
self.robot_kp, self.robot = _party("robot-a.example.com")
|
|
88
|
+
self.robot_did = self.robot_kp.did
|
|
89
|
+
self.capture = b"frame-0"
|
|
90
|
+
self.capture_hash = hash_capture(self.capture)
|
|
91
|
+
self.token = build_consent_token(
|
|
92
|
+
self.bystander,
|
|
93
|
+
bystander_did=self.by_kp.did,
|
|
94
|
+
capture_hash=self.capture_hash,
|
|
95
|
+
robot_did=self.robot_did,
|
|
96
|
+
valid_seconds=3600,
|
|
97
|
+
granted_at=T0,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def test_explicit_consent_evidence_verifies(self):
|
|
101
|
+
ev = build_consent_evidence(
|
|
102
|
+
self.robot,
|
|
103
|
+
robot_did=self.robot_did,
|
|
104
|
+
capture_hash=self.capture_hash,
|
|
105
|
+
basis="explicit-consent",
|
|
106
|
+
consent_tokens=[self.token],
|
|
107
|
+
valid_from=T0,
|
|
108
|
+
)
|
|
109
|
+
ok, subject = verify_consent_evidence(
|
|
110
|
+
ev,
|
|
111
|
+
self.robot_kp.public_key_jwk,
|
|
112
|
+
capture=self.capture,
|
|
113
|
+
consent_tokens=[self.token],
|
|
114
|
+
bystander_keys={self.by_kp.did: self.by_kp.public_key_jwk},
|
|
115
|
+
now=IN_WINDOW,
|
|
116
|
+
)
|
|
117
|
+
self.assertTrue(ok)
|
|
118
|
+
self.assertEqual(subject["basis"], "explicit-consent")
|
|
119
|
+
|
|
120
|
+
def test_explicit_consent_requires_a_token(self):
|
|
121
|
+
with self.assertRaises(RoboticsError):
|
|
122
|
+
build_consent_evidence(
|
|
123
|
+
self.robot,
|
|
124
|
+
robot_did=self.robot_did,
|
|
125
|
+
capture_hash=self.capture_hash,
|
|
126
|
+
basis="explicit-consent",
|
|
127
|
+
valid_from=T0,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def test_redacted_basis_needs_no_token(self):
|
|
131
|
+
ev = build_consent_evidence(
|
|
132
|
+
self.robot,
|
|
133
|
+
robot_did=self.robot_did,
|
|
134
|
+
capture_hash=self.capture_hash,
|
|
135
|
+
basis="redacted",
|
|
136
|
+
redaction_hash=hash_capture(b"blurred-frame"),
|
|
137
|
+
valid_from=T0,
|
|
138
|
+
)
|
|
139
|
+
ok, subject = verify_consent_evidence(
|
|
140
|
+
ev, self.robot_kp.public_key_jwk, capture=self.capture
|
|
141
|
+
)
|
|
142
|
+
self.assertTrue(ok)
|
|
143
|
+
self.assertEqual(subject["basis"], "redacted")
|
|
144
|
+
|
|
145
|
+
def test_wrong_capture_rejected(self):
|
|
146
|
+
ev = build_consent_evidence(
|
|
147
|
+
self.robot,
|
|
148
|
+
robot_did=self.robot_did,
|
|
149
|
+
capture_hash=self.capture_hash,
|
|
150
|
+
basis="posted-notice",
|
|
151
|
+
valid_from=T0,
|
|
152
|
+
)
|
|
153
|
+
ok, _ = verify_consent_evidence(
|
|
154
|
+
ev, self.robot_kp.public_key_jwk, capture=b"a-different-capture"
|
|
155
|
+
)
|
|
156
|
+
self.assertFalse(ok)
|
|
157
|
+
|
|
158
|
+
def test_unknown_basis_rejected_at_build(self):
|
|
159
|
+
with self.assertRaises(RoboticsError):
|
|
160
|
+
build_consent_evidence(
|
|
161
|
+
self.robot,
|
|
162
|
+
robot_did=self.robot_did,
|
|
163
|
+
capture_hash=self.capture_hash,
|
|
164
|
+
basis="whatever",
|
|
165
|
+
valid_from=T0,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def test_token_for_another_capture_does_not_satisfy_evidence(self):
|
|
169
|
+
other_hash = hash_capture(b"other-capture")
|
|
170
|
+
stray = build_consent_token(
|
|
171
|
+
self.bystander,
|
|
172
|
+
bystander_did=self.by_kp.did,
|
|
173
|
+
capture_hash=other_hash,
|
|
174
|
+
robot_did=self.robot_did,
|
|
175
|
+
valid_seconds=3600,
|
|
176
|
+
granted_at=T0,
|
|
177
|
+
)
|
|
178
|
+
ev = build_consent_evidence(
|
|
179
|
+
self.robot,
|
|
180
|
+
robot_did=self.robot_did,
|
|
181
|
+
capture_hash=self.capture_hash,
|
|
182
|
+
basis="explicit-consent",
|
|
183
|
+
consent_tokens=[stray],
|
|
184
|
+
valid_from=T0,
|
|
185
|
+
)
|
|
186
|
+
# The stray token is committed, but it is bound to a different capture, so
|
|
187
|
+
# verifying it against this evidence's capture fails.
|
|
188
|
+
ok, _ = verify_consent_evidence(
|
|
189
|
+
ev,
|
|
190
|
+
self.robot_kp.public_key_jwk,
|
|
191
|
+
capture=self.capture,
|
|
192
|
+
consent_tokens=[stray],
|
|
193
|
+
bystander_keys={self.by_kp.did: self.by_kp.public_key_jwk},
|
|
194
|
+
now=IN_WINDOW,
|
|
195
|
+
)
|
|
196
|
+
self.assertFalse(ok)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
unittest.main()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vouch Protocol MLflow Integration.
|
|
3
|
+
|
|
4
|
+
Sign a model artifact with a Vouch Credential at log or registration time, so a
|
|
5
|
+
registered model carries verifiable lineage: who registered it, on whose
|
|
6
|
+
authority, and a content digest that breaks if the weights are tampered with.
|
|
7
|
+
|
|
8
|
+
This module has no hard dependency on MLflow. The core helpers operate on a file
|
|
9
|
+
or directory path, so they work with any artifact store. If MLflow is installed,
|
|
10
|
+
log the returned credential as a tag or artifact on the run.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import os
|
|
17
|
+
from typing import Any, Dict, Optional, Tuple
|
|
18
|
+
|
|
19
|
+
from vouch import Signer, Verifier
|
|
20
|
+
|
|
21
|
+
_RESOURCE_PREFIX = "model:sha256:"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def compute_model_digest(path: str) -> str:
|
|
25
|
+
"""Return a SHA-256 hex digest over a model file, or a directory of files.
|
|
26
|
+
|
|
27
|
+
For a directory, every file is hashed in sorted relative-path order, and the
|
|
28
|
+
relative path is mixed in, so the digest is stable and order-independent.
|
|
29
|
+
"""
|
|
30
|
+
h = hashlib.sha256()
|
|
31
|
+
if os.path.isdir(path):
|
|
32
|
+
for root, _dirs, files in os.walk(path):
|
|
33
|
+
for name in sorted(files):
|
|
34
|
+
fp = os.path.join(root, name)
|
|
35
|
+
rel = os.path.relpath(fp, path).replace(os.sep, "/")
|
|
36
|
+
h.update(rel.encode("utf-8"))
|
|
37
|
+
h.update(b"\x00")
|
|
38
|
+
with open(fp, "rb") as f:
|
|
39
|
+
for chunk in iter(lambda: f.read(65536), b""):
|
|
40
|
+
h.update(chunk)
|
|
41
|
+
else:
|
|
42
|
+
with open(path, "rb") as f:
|
|
43
|
+
for chunk in iter(lambda: f.read(65536), b""):
|
|
44
|
+
h.update(chunk)
|
|
45
|
+
return h.hexdigest()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def sign_model(
|
|
49
|
+
signer: Signer,
|
|
50
|
+
path: str,
|
|
51
|
+
*,
|
|
52
|
+
name: Optional[str] = None,
|
|
53
|
+
parent_credential: Optional[Dict[str, Any]] = None,
|
|
54
|
+
hybrid: bool = False,
|
|
55
|
+
valid_seconds: Optional[int] = None,
|
|
56
|
+
) -> Dict[str, Any]:
|
|
57
|
+
"""Issue a Vouch Credential binding the model at ``path`` to the signer.
|
|
58
|
+
|
|
59
|
+
The credential's ``resource`` is the model's content digest, so any change to
|
|
60
|
+
the weights breaks verification.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
signer: The registering principal's Signer.
|
|
64
|
+
path: Path to the model file or directory.
|
|
65
|
+
name: Human label for the model. Defaults to the basename of ``path``.
|
|
66
|
+
parent_credential: Optional parent credential to extend (delegation).
|
|
67
|
+
hybrid: Issue under the post-quantum hybrid profile when True.
|
|
68
|
+
valid_seconds: Optional validity window override.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
A signed Vouch Credential dict. Log it as an MLflow tag or artifact.
|
|
72
|
+
"""
|
|
73
|
+
digest = compute_model_digest(path)
|
|
74
|
+
target = name or os.path.basename(os.path.normpath(path)) or "model"
|
|
75
|
+
intent = {"action": "register", "target": target, "resource": f"{_RESOURCE_PREFIX}{digest}"}
|
|
76
|
+
issue = signer.sign_hybrid if hybrid else signer.sign
|
|
77
|
+
return issue(
|
|
78
|
+
intent=intent,
|
|
79
|
+
parent_credential=parent_credential,
|
|
80
|
+
valid_seconds=valid_seconds,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def verify_model(
|
|
85
|
+
path: str,
|
|
86
|
+
credential: Dict[str, Any],
|
|
87
|
+
public_key: Optional[Any] = None,
|
|
88
|
+
) -> Tuple[bool, Optional[Any]]:
|
|
89
|
+
"""Verify a model credential AND that the on-disk content still matches.
|
|
90
|
+
|
|
91
|
+
Returns ``(is_valid, passport)``. ``is_valid`` is False if the signature
|
|
92
|
+
fails OR the recomputed digest does not match the credential's bound digest.
|
|
93
|
+
"""
|
|
94
|
+
ok, passport = Verifier.verify(credential, public_key=public_key)
|
|
95
|
+
if not ok:
|
|
96
|
+
return False, passport
|
|
97
|
+
intent = (credential.get("credentialSubject") or {}).get("intent") or {}
|
|
98
|
+
bound = intent.get("resource")
|
|
99
|
+
if bound != f"{_RESOURCE_PREFIX}{compute_model_digest(path)}":
|
|
100
|
+
return False, passport
|
|
101
|
+
return True, passport
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vouch Protocol safetensors Integration.
|
|
3
|
+
|
|
4
|
+
Embed a Vouch Credential in a .safetensors file's existing ``__metadata__``
|
|
5
|
+
header field, with zero changes to the safetensors format or its Rust core.
|
|
6
|
+
|
|
7
|
+
This is deliberately complementary to OpenSSF Model Signing (OMS). OMS proves an
|
|
8
|
+
artifact is intact and signed by a key. Vouch adds the agent and delegation
|
|
9
|
+
dimension: which principal or pipeline produced the weights, traceable back to an
|
|
10
|
+
accountable human. The credential is bound to a SHA-256 of the tensor data
|
|
11
|
+
buffer, so any change to the weights breaks verification. Standard loaders
|
|
12
|
+
(including Hugging Face) ignore the extra metadata key, so signed files load
|
|
13
|
+
normally.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import hashlib
|
|
19
|
+
import json
|
|
20
|
+
import struct
|
|
21
|
+
from typing import Any, Dict, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
from vouch import Signer, Verifier
|
|
24
|
+
|
|
25
|
+
_META_KEY = "vouch_credential"
|
|
26
|
+
_RESOURCE_PREFIX = "model:safetensors:sha256:"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _read(path: str) -> Tuple[Dict[str, Any], bytes]:
|
|
30
|
+
"""Return (header_dict, tensor_data_bytes) from a safetensors file."""
|
|
31
|
+
with open(path, "rb") as f:
|
|
32
|
+
header_len = struct.unpack("<Q", f.read(8))[0]
|
|
33
|
+
header = json.loads(f.read(header_len).decode("utf-8"))
|
|
34
|
+
data = f.read()
|
|
35
|
+
return header, data
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _write(path: str, header: Dict[str, Any], data: bytes) -> None:
|
|
39
|
+
"""Write a safetensors file from a header dict and the tensor data buffer."""
|
|
40
|
+
header_bytes = json.dumps(header, separators=(",", ":")).encode("utf-8")
|
|
41
|
+
with open(path, "wb") as f:
|
|
42
|
+
f.write(struct.pack("<Q", len(header_bytes)))
|
|
43
|
+
f.write(header_bytes)
|
|
44
|
+
f.write(data)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def tensor_data_digest(path: str) -> str:
|
|
48
|
+
"""Return the SHA-256 hex digest of the tensor data buffer (excludes header)."""
|
|
49
|
+
_header, data = _read(path)
|
|
50
|
+
return hashlib.sha256(data).hexdigest()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def sign_safetensors(
|
|
54
|
+
signer: Signer,
|
|
55
|
+
path: str,
|
|
56
|
+
*,
|
|
57
|
+
out_path: Optional[str] = None,
|
|
58
|
+
name: Optional[str] = None,
|
|
59
|
+
parent_credential: Optional[Dict[str, Any]] = None,
|
|
60
|
+
hybrid: bool = False,
|
|
61
|
+
valid_seconds: Optional[int] = None,
|
|
62
|
+
) -> Dict[str, Any]:
|
|
63
|
+
"""Embed a Vouch Credential in a safetensors file's ``__metadata__``.
|
|
64
|
+
|
|
65
|
+
The credential binds to a digest of the tensor data buffer. The header gains
|
|
66
|
+
one string key; the tensor bytes are untouched.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
signer: The producing principal's Signer.
|
|
70
|
+
path: Path to the input .safetensors file.
|
|
71
|
+
out_path: Where to write the signed file. Defaults to ``path`` (in place).
|
|
72
|
+
name: Human label for the model.
|
|
73
|
+
parent_credential: Optional parent credential to extend (delegation).
|
|
74
|
+
hybrid: Issue under the post-quantum hybrid profile when True.
|
|
75
|
+
valid_seconds: Optional validity window override.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The signed Vouch Credential dict (also embedded in the file).
|
|
79
|
+
"""
|
|
80
|
+
header, data = _read(path)
|
|
81
|
+
digest = hashlib.sha256(data).hexdigest()
|
|
82
|
+
intent = {
|
|
83
|
+
"action": "register",
|
|
84
|
+
"target": name or "safetensors-model",
|
|
85
|
+
"resource": f"{_RESOURCE_PREFIX}{digest}",
|
|
86
|
+
}
|
|
87
|
+
issue = signer.sign_hybrid if hybrid else signer.sign
|
|
88
|
+
credential = issue(
|
|
89
|
+
intent=intent,
|
|
90
|
+
parent_credential=parent_credential,
|
|
91
|
+
valid_seconds=valid_seconds,
|
|
92
|
+
)
|
|
93
|
+
metadata = dict(header.get("__metadata__") or {})
|
|
94
|
+
metadata[_META_KEY] = json.dumps(credential, separators=(",", ":"))
|
|
95
|
+
header["__metadata__"] = metadata
|
|
96
|
+
_write(out_path or path, header, data)
|
|
97
|
+
return credential
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def read_embedded_credential(path: str) -> Optional[Dict[str, Any]]:
|
|
101
|
+
"""Return the embedded Vouch Credential, or None if the file is unsigned."""
|
|
102
|
+
header, _data = _read(path)
|
|
103
|
+
raw = (header.get("__metadata__") or {}).get(_META_KEY)
|
|
104
|
+
return json.loads(raw) if raw else None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def verify_safetensors(
|
|
108
|
+
path: str,
|
|
109
|
+
public_key: Optional[Any] = None,
|
|
110
|
+
) -> Tuple[bool, Optional[Any]]:
|
|
111
|
+
"""Verify a signed safetensors file: signature AND tensor-data integrity.
|
|
112
|
+
|
|
113
|
+
Returns ``(is_valid, passport)``. ``is_valid`` is False if the file is
|
|
114
|
+
unsigned, the signature fails, or the tensor data no longer matches the
|
|
115
|
+
bound digest.
|
|
116
|
+
"""
|
|
117
|
+
credential = read_embedded_credential(path)
|
|
118
|
+
if not credential:
|
|
119
|
+
return False, None
|
|
120
|
+
ok, passport = Verifier.verify(credential, public_key=public_key)
|
|
121
|
+
if not ok:
|
|
122
|
+
return False, passport
|
|
123
|
+
bound = (credential.get("credentialSubject") or {}).get("intent", {}).get("resource")
|
|
124
|
+
if bound != f"{_RESOURCE_PREFIX}{tensor_data_digest(path)}":
|
|
125
|
+
return False, passport
|
|
126
|
+
return True, passport
|
|
@@ -24,6 +24,7 @@ robots and embodied agents:
|
|
|
24
24
|
- access: bounded, revocable robot access to physical infrastructure resources.
|
|
25
25
|
- fusion: signed provenance binding a fused world model to its input frames.
|
|
26
26
|
- wear: signed wear and degradation attestation with capability auto-attenuation.
|
|
27
|
+
- consent: bystander-consent evidence binding a consent basis to a robot capture.
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
30
|
from .capability import (
|
|
@@ -170,6 +171,16 @@ from .wear import (
|
|
|
170
171
|
verify_wear_attestation,
|
|
171
172
|
verify_wear_chain,
|
|
172
173
|
)
|
|
174
|
+
from .consent import (
|
|
175
|
+
CONSENT_BASES,
|
|
176
|
+
CONSENT_EVIDENCE_TYPE,
|
|
177
|
+
CONSENT_TOKEN_TYPE,
|
|
178
|
+
build_consent_evidence,
|
|
179
|
+
build_consent_token,
|
|
180
|
+
hash_capture,
|
|
181
|
+
verify_consent_evidence,
|
|
182
|
+
verify_consent_token,
|
|
183
|
+
)
|
|
173
184
|
|
|
174
185
|
__all__ = [
|
|
175
186
|
# identity
|
|
@@ -287,6 +298,15 @@ __all__ = [
|
|
|
287
298
|
"verify_wear_attestation",
|
|
288
299
|
"verify_wear_chain",
|
|
289
300
|
"attenuate_for_wear",
|
|
301
|
+
# bystander consent (consent basis bound to a robot capture, privacy-preserving)
|
|
302
|
+
"CONSENT_EVIDENCE_TYPE",
|
|
303
|
+
"CONSENT_TOKEN_TYPE",
|
|
304
|
+
"CONSENT_BASES",
|
|
305
|
+
"hash_capture",
|
|
306
|
+
"build_consent_token",
|
|
307
|
+
"verify_consent_token",
|
|
308
|
+
"build_consent_evidence",
|
|
309
|
+
"verify_consent_evidence",
|
|
290
310
|
# safety record (incident/near-miss ledger + portable record)
|
|
291
311
|
"SafetyEventLog",
|
|
292
312
|
"verify_safety_log",
|