proofbundle 3.1.2__tar.gz → 3.2.0__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.
- {proofbundle-3.1.2/src/proofbundle.egg-info → proofbundle-3.2.0}/PKG-INFO +10 -4
- {proofbundle-3.1.2 → proofbundle-3.2.0}/README.md +9 -3
- {proofbundle-3.1.2 → proofbundle-3.2.0}/pyproject.toml +1 -1
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/__init__.py +43 -2
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_chia_add.py +51 -18
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/bundle.py +56 -7
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/cli.py +348 -13
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/decision.py +5 -1
- proofbundle-3.2.0/src/proofbundle/evidence_pack.py +98 -0
- proofbundle-3.2.0/src/proofbundle/hashalg.py +177 -0
- proofbundle-3.2.0/src/proofbundle/outcome.py +358 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policies/decision-receipt-template-v1.json +1 -0
- proofbundle-3.2.0/src/proofbundle/policies/research-preview-v1.json +16 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policies/strict-eval-authenticated-root-template-v1.json +1 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policies/strict-eval-template-v1.json +1 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policies/strict-prereg-template-v1.json +1 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policy.py +302 -18
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policy_profiles.py +30 -6
- proofbundle-3.2.0/src/proofbundle/pqsig.py +114 -0
- proofbundle-3.2.0/src/proofbundle/public_transparency.py +201 -0
- proofbundle-3.2.0/src/proofbundle/renewal.py +483 -0
- proofbundle-3.2.0/src/proofbundle/run_ledger.py +295 -0
- proofbundle-3.2.0/src/proofbundle/sdjwt_vc.py +182 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/signature.py +2 -0
- proofbundle-3.2.0/src/proofbundle/subject_binding.py +115 -0
- proofbundle-3.2.0/src/proofbundle/trust_pack.py +432 -0
- proofbundle-3.2.0/src/proofbundle/verification_summary.py +252 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0/src/proofbundle.egg-info}/PKG-INFO +10 -4
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/SOURCES.txt +34 -0
- proofbundle-3.2.0/tests/test_anchor_longevity_property.py +169 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_chia_add.py +32 -0
- proofbundle-3.2.0/tests/test_checkpoint_quorum_property.py +85 -0
- proofbundle-3.2.0/tests/test_content_root_property.py +132 -0
- proofbundle-3.2.0/tests/test_dsse_adversarial.py +102 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_evalclaim.py +15 -0
- proofbundle-3.2.0/tests/test_evidence_pack.py +147 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_fuzz_parsers.py +77 -0
- proofbundle-3.2.0/tests/test_hashalg.py +145 -0
- proofbundle-3.2.0/tests/test_lens_review_fixes_3_1_3.py +252 -0
- proofbundle-3.2.0/tests/test_merkle_consistency_property.py +83 -0
- proofbundle-3.2.0/tests/test_official_vectors.py +94 -0
- proofbundle-3.2.0/tests/test_outcome_verify.py +281 -0
- proofbundle-3.2.0/tests/test_policy_lifecycle_purpose.py +464 -0
- proofbundle-3.2.0/tests/test_pqsig.py +126 -0
- proofbundle-3.2.0/tests/test_public_transparency.py +193 -0
- proofbundle-3.2.0/tests/test_renewal.py +179 -0
- proofbundle-3.2.0/tests/test_renewal_policy.py +100 -0
- proofbundle-3.2.0/tests/test_renewal_signed.py +214 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_rfc6962_external_vectors.py +20 -5
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_root_authenticity.py +44 -13
- proofbundle-3.2.0/tests/test_run_ledger.py +152 -0
- proofbundle-3.2.0/tests/test_sdjwt_adversarial.py +134 -0
- proofbundle-3.2.0/tests/test_sdjwt_vc.py +222 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_statuslist.py +46 -0
- proofbundle-3.2.0/tests/test_subject_binding.py +95 -0
- proofbundle-3.2.0/tests/test_tree_context_authenticity.py +329 -0
- proofbundle-3.2.0/tests/test_trust_pack.py +430 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_trust_policy.py +4 -2
- proofbundle-3.2.0/tests/test_verification_summary.py +167 -0
- proofbundle-3.1.2/src/proofbundle/policies/research-preview-v1.json +0 -7
- {proofbundle-3.1.2 → proofbundle-3.2.0}/LICENSE +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/setup.cfg +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/_inspect_registry.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/_integration.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/_strict_json.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/__init__.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/_provenance.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/eee.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/inspect_ai.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/lm_eval.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/promptfoo.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/samples.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_chia.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_markovian.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_ots.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_rfc3161.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/beacon.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/canonical.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/checkpoint.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/demo.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/dsse.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/eee_eval_schema.json +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/emit.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/errors.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/evalclaim.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/experimental/__init__.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/experimental/enclave.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/hf_evals.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/inspect_hook.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/intoto.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/kbjwt.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/merkle.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/persample.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/prereg.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/py.typed +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/pytest_plugin.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/sdjwt.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/sdjwt_issue.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/statuslist.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/tlogproof.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/dependency_links.txt +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/entry_points.txt +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/requires.txt +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/top_level.txt +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_adapters.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_adversarial.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchor_target_trustedtime.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_chia.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_chia_claims.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_generic.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_markovian.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_ots.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_rfc3161.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_beacon.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_branch_base_check.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_bundle.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_bundle_robustness.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_canonical.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_checkpoint.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_claims_hygiene.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cli.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cli_eval.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cli_require_anchor.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_conformance.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cosignature.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cosignature_mldsa.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_anchors.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_cli.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_evidence.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_fuzz.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_hardening.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_policy.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_schema.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_validator_api.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_verify.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_demo.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_docs_truth.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_dup_key_reject.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_ed25519_semantics.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_eee.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_emit.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_eval_claim_schema.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_eval_evidence_class.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_examples.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_experimental_enclave.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_fork_pr_secret_isolation.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_hf_entry_verify_eee_digest.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_hf_evals.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_inspect_hook.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_claims_hygiene.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_content_root_migration.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_dsse.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_eval_result.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_examples.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_spec_diff.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_svr.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_kbjwt.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_merkle.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_merkle_property.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_ok_semantics.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_oversized_int_guard.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_persample.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_pinned_key_validation.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_policy_explain_lint.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_policy_profiles.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_policy_templates.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_predicate_type_enforcement.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_prereg.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_promptfoo.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_provenance.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_pytest_plugin.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_rekor_interop.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_release_integrity_gate.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_schema.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_sdjwt_duplicate_cnf.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_sdjwt_issue.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_sdjwt_reference.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_sdjwt_verify_binding.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_signature.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_tlogproof.py +0 -0
- {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_verify_matrix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proofbundle
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT.
|
|
5
5
|
Author: Konrad Gruszka
|
|
6
6
|
License: MIT
|
|
@@ -127,9 +127,11 @@ proofbundle verify receipt.json --policy trust_policy.json # POLICY: OK | FAIL
|
|
|
127
127
|
# start from a shipped TEMPLATE and pin your own signer — offline, no network:
|
|
128
128
|
proofbundle policy instantiate strict-eval-template-v1 \
|
|
129
129
|
--issuer-key org-eval.pub --policy-id org/strict-eval-v1 --output org.json
|
|
130
|
-
proofbundle verify receipt.json --json --policy org.json
|
|
131
|
-
|
|
132
|
-
#
|
|
130
|
+
proofbundle verify receipt.json --json --policy org.json \
|
|
131
|
+
--expected-root <b64> --expected-tree-size <n> # or: --trusted-checkpoint note.txt --checkpoint-vkey <vkey>
|
|
132
|
+
# → root_authenticity.safeForAutomation: true only when the policy pins a trusted signer AND root
|
|
133
|
+
# plus tree size are authenticated ATOMICALLY from one source (a root-bytes-only pin is
|
|
134
|
+
# rootTrustLevel: ROOT_BYTES_ONLY — never automation-safe; automationBlockers names every reason)
|
|
133
135
|
```
|
|
134
136
|
|
|
135
137
|
## Inspect-native? (METR Task Standard, UK-AISI ecosystem)
|
|
@@ -298,6 +300,10 @@ roadmap.
|
|
|
298
300
|
a signed outcome is self-assertion. See
|
|
299
301
|
[docs/predicates/decision-receipt.md](https://github.com/b7n0de/proofbundle/blob/main/docs/predicates/decision-receipt.md)
|
|
300
302
|
and [ADR 0001](https://github.com/b7n0de/proofbundle/blob/main/docs/adr/0001-decision-receipt-separate-predicate.md).
|
|
303
|
+
That signed outcome is the `action-outcome/v0.1` predicate (EXPERIMENTAL, 3.2.0): an executor signs what was
|
|
304
|
+
actually done, bound by content root to the Decision Receipt, with role separation (executor ≠ decision maker)
|
|
305
|
+
and `execution_proven` only when an effect digest backs an `executed` status. See
|
|
306
|
+
[docs/predicates/action-outcome.md](https://github.com/b7n0de/proofbundle/blob/main/docs/predicates/action-outcome.md).
|
|
301
307
|
|
|
302
308
|
## Docs
|
|
303
309
|
|
|
@@ -73,9 +73,11 @@ proofbundle verify receipt.json --policy trust_policy.json # POLICY: OK | FAIL
|
|
|
73
73
|
# start from a shipped TEMPLATE and pin your own signer — offline, no network:
|
|
74
74
|
proofbundle policy instantiate strict-eval-template-v1 \
|
|
75
75
|
--issuer-key org-eval.pub --policy-id org/strict-eval-v1 --output org.json
|
|
76
|
-
proofbundle verify receipt.json --json --policy org.json
|
|
77
|
-
|
|
78
|
-
#
|
|
76
|
+
proofbundle verify receipt.json --json --policy org.json \
|
|
77
|
+
--expected-root <b64> --expected-tree-size <n> # or: --trusted-checkpoint note.txt --checkpoint-vkey <vkey>
|
|
78
|
+
# → root_authenticity.safeForAutomation: true only when the policy pins a trusted signer AND root
|
|
79
|
+
# plus tree size are authenticated ATOMICALLY from one source (a root-bytes-only pin is
|
|
80
|
+
# rootTrustLevel: ROOT_BYTES_ONLY — never automation-safe; automationBlockers names every reason)
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
## Inspect-native? (METR Task Standard, UK-AISI ecosystem)
|
|
@@ -244,6 +246,10 @@ roadmap.
|
|
|
244
246
|
a signed outcome is self-assertion. See
|
|
245
247
|
[docs/predicates/decision-receipt.md](https://github.com/b7n0de/proofbundle/blob/main/docs/predicates/decision-receipt.md)
|
|
246
248
|
and [ADR 0001](https://github.com/b7n0de/proofbundle/blob/main/docs/adr/0001-decision-receipt-separate-predicate.md).
|
|
249
|
+
That signed outcome is the `action-outcome/v0.1` predicate (EXPERIMENTAL, 3.2.0): an executor signs what was
|
|
250
|
+
actually done, bound by content root to the Decision Receipt, with role separation (executor ≠ decision maker)
|
|
251
|
+
and `execution_proven` only when an effect digest backs an `executed` status. See
|
|
252
|
+
[docs/predicates/action-outcome.md](https://github.com/b7n0de/proofbundle/blob/main/docs/predicates/action-outcome.md).
|
|
247
253
|
|
|
248
254
|
## Docs
|
|
249
255
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proofbundle"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.2.0"
|
|
8
8
|
description = "Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -13,12 +13,12 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
from typing import TYPE_CHECKING
|
|
15
15
|
|
|
16
|
-
__version__ = "3.
|
|
16
|
+
__version__ = "3.2.0"
|
|
17
17
|
|
|
18
18
|
# The `proofbundle/v0.1` normative spec revision this build implements — kept in sync with the
|
|
19
19
|
# `Revision:` line at the top of SPEC.md by tests/test_docs_truth.py (WP-B1, closes #28). Bump
|
|
20
20
|
# both together whenever SPEC.md's normative text changes (not on every package release).
|
|
21
|
-
SPEC_REVISION = "2026-07-
|
|
21
|
+
SPEC_REVISION = "2026-07-13"
|
|
22
22
|
|
|
23
23
|
__all__ = [
|
|
24
24
|
"__version__",
|
|
@@ -47,6 +47,23 @@ __all__ = [
|
|
|
47
47
|
"beacon_audit_challenge",
|
|
48
48
|
"canonicalize_statement",
|
|
49
49
|
"statement_content_root",
|
|
50
|
+
"resolve_hash_alg",
|
|
51
|
+
"compute_dual_hash",
|
|
52
|
+
"verify_dual_hash",
|
|
53
|
+
"build_evidence_pack",
|
|
54
|
+
"verify_evidence_pack",
|
|
55
|
+
"ots_upgraded_proof_is_self_contained",
|
|
56
|
+
"build_initial_sequence",
|
|
57
|
+
"renew_timestamp",
|
|
58
|
+
"renew_hashtree",
|
|
59
|
+
"verify_sequence",
|
|
60
|
+
"last_ats",
|
|
61
|
+
"evaluate_renewal_policy",
|
|
62
|
+
"ArchiveTimeStamp",
|
|
63
|
+
"RenewalPolicy",
|
|
64
|
+
"verify_mldsa",
|
|
65
|
+
"verify_slhdsa",
|
|
66
|
+
"verify_hybrid",
|
|
50
67
|
"VerificationResult",
|
|
51
68
|
"Check",
|
|
52
69
|
"ProofBundleError",
|
|
@@ -75,6 +92,23 @@ _LAZY = {
|
|
|
75
92
|
"beacon_audit_challenge": ".beacon",
|
|
76
93
|
"canonicalize_statement": ".canonical",
|
|
77
94
|
"statement_content_root": ".canonical",
|
|
95
|
+
"resolve_hash_alg": ".hashalg",
|
|
96
|
+
"compute_dual_hash": ".hashalg",
|
|
97
|
+
"verify_dual_hash": ".hashalg",
|
|
98
|
+
"build_evidence_pack": ".evidence_pack",
|
|
99
|
+
"verify_evidence_pack": ".evidence_pack",
|
|
100
|
+
"ots_upgraded_proof_is_self_contained": ".evidence_pack",
|
|
101
|
+
"build_initial_sequence": ".renewal",
|
|
102
|
+
"renew_timestamp": ".renewal",
|
|
103
|
+
"renew_hashtree": ".renewal",
|
|
104
|
+
"verify_sequence": ".renewal",
|
|
105
|
+
"last_ats": ".renewal",
|
|
106
|
+
"evaluate_renewal_policy": ".renewal",
|
|
107
|
+
"ArchiveTimeStamp": ".renewal",
|
|
108
|
+
"RenewalPolicy": ".renewal",
|
|
109
|
+
"verify_mldsa": ".pqsig",
|
|
110
|
+
"verify_slhdsa": ".pqsig",
|
|
111
|
+
"verify_hybrid": ".pqsig",
|
|
78
112
|
}
|
|
79
113
|
|
|
80
114
|
if TYPE_CHECKING: # static analysers + IDEs see the real names/types; runtime stays lazy
|
|
@@ -88,6 +122,13 @@ if TYPE_CHECKING: # static analysers + IDEs see the real names/types; runtime s
|
|
|
88
122
|
verify_sample_opening)
|
|
89
123
|
from .beacon import beacon_audit_challenge
|
|
90
124
|
from .canonical import canonicalize_statement, statement_content_root
|
|
125
|
+
from .hashalg import compute_dual_hash, resolve_hash_alg, verify_dual_hash
|
|
126
|
+
from .evidence_pack import (build_evidence_pack, ots_upgraded_proof_is_self_contained,
|
|
127
|
+
verify_evidence_pack)
|
|
128
|
+
from .renewal import (ArchiveTimeStamp, RenewalPolicy, build_initial_sequence,
|
|
129
|
+
evaluate_renewal_policy, last_ats, renew_hashtree, renew_timestamp,
|
|
130
|
+
verify_sequence)
|
|
131
|
+
from .pqsig import verify_hybrid, verify_mldsa, verify_slhdsa
|
|
91
132
|
from .prereg import prereg_hash, verify_prereg
|
|
92
133
|
from .statuslist import verify_status_snapshot
|
|
93
134
|
from .tlogproof import verify_tlog_proof
|
|
@@ -19,13 +19,41 @@ import os
|
|
|
19
19
|
import shutil
|
|
20
20
|
import subprocess
|
|
21
21
|
import time
|
|
22
|
-
from
|
|
22
|
+
from contextlib import contextmanager
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Iterator, Optional
|
|
23
25
|
|
|
24
26
|
from .anchors_chia import ANCHOR_TYPE, verify_offline_merkle
|
|
25
27
|
|
|
26
28
|
_CHIA_BIN = os.getenv("CHIA_CLI", shutil.which("chia") or "chia")
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
@contextmanager
|
|
32
|
+
def _anchor_in_progress_lock(lock_path: Optional[str]) -> Iterator[None]:
|
|
33
|
+
"""Advisory 'anchor in progress' marker held around the on-chain batch_update + confirmation.
|
|
34
|
+
|
|
35
|
+
While an anchor's wallet-using transaction is submitted and confirmed, write a marker FILE at
|
|
36
|
+
``lock_path`` (or the ``PROOFBUNDLE_ANCHOR_LOCK_PATH`` env). A SEPARATE process — e.g. a wallet-switch
|
|
37
|
+
guard that must not log the wallet out from under the transaction — can treat the file's PRESENCE as
|
|
38
|
+
'an anchor is running'. Removed on completion; a crash leaves it stale, which a conservative guard
|
|
39
|
+
treats as still-active (fail-safe, never breaks a possibly-live anchor). No-op when no path is set, so
|
|
40
|
+
the default anchor flow is unchanged and proofbundle stays free of any consumer-specific naming."""
|
|
41
|
+
path = lock_path or os.getenv("PROOFBUNDLE_ANCHOR_LOCK_PATH")
|
|
42
|
+
if not path:
|
|
43
|
+
yield
|
|
44
|
+
return
|
|
45
|
+
p = Path(path)
|
|
46
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
p.write_text(str(os.getpid()))
|
|
48
|
+
try:
|
|
49
|
+
yield
|
|
50
|
+
finally:
|
|
51
|
+
try:
|
|
52
|
+
p.unlink()
|
|
53
|
+
except OSError:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
29
57
|
class ChiaRpcError(RuntimeError):
|
|
30
58
|
"""A Chia RPC/CLI call failed. Fail-closed: the caller aborts, no partial anchor is written."""
|
|
31
59
|
|
|
@@ -125,11 +153,15 @@ def _wait_confirmed(store_id: str, prev_root: Optional[str], *, timeout: int = 1
|
|
|
125
153
|
|
|
126
154
|
def anchor_add(canonical_root_hex: str, *, store_id: str, value_digest_hex: Optional[str] = None,
|
|
127
155
|
target: str = "receipt", network: str = "mainnet", fee: int = 100_000_000,
|
|
128
|
-
wait: bool = True) -> dict:
|
|
156
|
+
wait: bool = True, lock_path: Optional[str] = None) -> dict:
|
|
129
157
|
"""Full flow: insert ``key=canonicalRoot`` (value = ``value_digest`` or the canonicalRoot itself) into the
|
|
130
158
|
store, wait for on-chain confirmation, then export the anchor. Idempotent: a key already present ("Key
|
|
131
159
|
already present") is treated as "already anchored" and re-exported, not an error. Fail-closed: a network
|
|
132
|
-
failure raises ChiaRpcError and writes NOTHING partial (the caller keeps its receipt untouched).
|
|
160
|
+
failure raises ChiaRpcError and writes NOTHING partial (the caller keeps its receipt untouched).
|
|
161
|
+
|
|
162
|
+
``lock_path`` (or the ``PROOFBUNDLE_ANCHOR_LOCK_PATH`` env) holds an advisory 'anchor in progress' marker
|
|
163
|
+
file AUTOMATICALLY around the wallet-using batch_update + confirmation, so a separate wallet-switch guard
|
|
164
|
+
never logs the wallet out mid-anchor. No path → unchanged behaviour."""
|
|
133
165
|
canonical_root_hex = canonical_root_hex if canonical_root_hex.startswith(("0x", "0X")) else "0x" + canonical_root_hex
|
|
134
166
|
canonical_root = bytes.fromhex(canonical_root_hex[2:])
|
|
135
167
|
if len(canonical_root) != 32:
|
|
@@ -137,18 +169,19 @@ def anchor_add(canonical_root_hex: str, *, store_id: str, value_digest_hex: Opti
|
|
|
137
169
|
value_hex = value_digest_hex or canonical_root_hex
|
|
138
170
|
value_hex = value_hex if value_hex.startswith(("0x", "0X")) else "0x" + value_hex
|
|
139
171
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
already
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
with _anchor_in_progress_lock(lock_path):
|
|
173
|
+
prev_root = _rpc("data_layer", "get_root", {"id": store_id}).get("hash")
|
|
174
|
+
changelist = [{"action": "insert", "key": canonical_root_hex, "value": value_hex}]
|
|
175
|
+
already = False
|
|
176
|
+
try:
|
|
177
|
+
_rpc("data_layer", "batch_update",
|
|
178
|
+
{"id": store_id, "changelist": changelist, "fee": fee, "submit_on_chain": True})
|
|
179
|
+
except ChiaRpcError as exc:
|
|
180
|
+
if "already present" in str(exc).lower():
|
|
181
|
+
already = True # idempotent: the digest is already anchored
|
|
182
|
+
else:
|
|
183
|
+
raise # any other failure → clean abort, nothing written
|
|
184
|
+
if wait and not already:
|
|
185
|
+
_wait_confirmed(store_id, prev_root)
|
|
186
|
+
return export_anchor(store_id, canonical_root=canonical_root,
|
|
187
|
+
target=target, network=network, value=value_hex)
|
|
@@ -43,12 +43,18 @@ AUTOMATION_BLOCKER_REASONS = {
|
|
|
43
43
|
"CRYPTO_FAILED": "Cryptographic verification did not pass",
|
|
44
44
|
"ROOT_NOT_AUTHENTICATED": "The Merkle root was not authenticated against a relying-party value "
|
|
45
45
|
"(--expected-root or a policy trusted_roots entry)",
|
|
46
|
+
"TREE_CONTEXT_NOT_AUTHENTICATED": "Root and tree size were not authenticated ATOMICALLY from one "
|
|
47
|
+
"source (a signed checkpoint via --trusted-checkpoint / policy "
|
|
48
|
+
"trusted_checkpoints, or an --expected-root + "
|
|
49
|
+
"--expected-tree-size pair) — a root-bytes-only pin cannot "
|
|
50
|
+
"detect a tree-size/leaf-index relabel (A-P0-1)",
|
|
46
51
|
"POLICY_NOT_EVALUATED": "No trust policy was evaluated (supply --policy to authorise a signer)",
|
|
47
52
|
"POLICY_FAILED": "The supplied trust policy was not satisfied",
|
|
48
53
|
"SIGNER_NOT_PINNED": "The trust policy pins no trusted signer identity (attributes to nobody)",
|
|
49
54
|
"TEMPLATE_NOT_INSTANTIATED": "The trust policy is a raw template (requiresIdentityOverlay:true) — "
|
|
50
55
|
"instantiate it with a signer identity before depending on it for automation",
|
|
51
56
|
"POLICY_EXPIRED": "The trust policy has expired (its valid_until is in the past)",
|
|
57
|
+
"POLICY_NOT_YET_VALID": "The trust policy is not yet valid (its valid_from is in the future)",
|
|
52
58
|
"POLICY_WARNINGS_PRESENT": "The trust policy carries a warning that blocks automation",
|
|
53
59
|
"ANCHOR_REQUIRED_FAILED": "A required external time anchor did not verify",
|
|
54
60
|
"PUBLIC_TRANSPARENCY_REQUIRED_FAILED": "A required public-transparency proof did not verify",
|
|
@@ -433,9 +439,12 @@ def root_authenticity_summary(result: VerificationResult, *,
|
|
|
433
439
|
signer_trusted: Optional[bool] = None,
|
|
434
440
|
policy_warnings: Optional[list] = None,
|
|
435
441
|
policy_expired: Optional[bool] = None,
|
|
442
|
+
policy_not_yet_valid: Optional[bool] = None,
|
|
436
443
|
requires_identity_overlay: Optional[bool] = None,
|
|
437
444
|
public_transparency_ok: Optional[bool] = None,
|
|
438
|
-
replay_ok: Optional[bool] = None
|
|
445
|
+
replay_ok: Optional[bool] = None,
|
|
446
|
+
tree_context_authenticated: Optional[bool] = None,
|
|
447
|
+
checkpoint_authenticity: Optional[str] = None) -> dict:
|
|
439
448
|
"""Structured root-authenticity verdicts (P0-A §6.3), derived from a completed VerificationResult.
|
|
440
449
|
|
|
441
450
|
Separates what Merkle inclusion actually proves from what it does NOT, as three-state strings so a
|
|
@@ -473,6 +482,28 @@ def root_authenticity_summary(result: VerificationResult, *,
|
|
|
473
482
|
root_auth = "FAIL"
|
|
474
483
|
else:
|
|
475
484
|
root_auth = "NOT_EVALUATED"
|
|
485
|
+
|
|
486
|
+
# A-P0-1 §5.3: the differentiated tree-context verdicts. `root_auth` above is the root-BYTES
|
|
487
|
+
# verdict; TREE_CONTEXT_AUTHENTICITY additionally requires that root and tree_size were
|
|
488
|
+
# authenticated ATOMICALLY from one source (a signed checkpoint, a policy trusted_checkpoints
|
|
489
|
+
# match, or an RP-supplied root+size PAIR). A naked root pin reaches at most
|
|
490
|
+
# ROOT_BYTES_AUTHENTICITY: PASS — never TREE_CONTEXT_AUTHENTICITY: PASS (§5.5).
|
|
491
|
+
if tree_context_authenticated is True:
|
|
492
|
+
tree_context = "PASS"
|
|
493
|
+
elif tree_context_authenticated is False:
|
|
494
|
+
tree_context = "FAIL"
|
|
495
|
+
else:
|
|
496
|
+
tree_context = "NOT_EVALUATED"
|
|
497
|
+
cp_auth = checkpoint_authenticity if checkpoint_authenticity in ("PASS", "FAIL") \
|
|
498
|
+
else "NOT_EVALUATED"
|
|
499
|
+
if tree_context == "PASS" and cp_auth == "PASS":
|
|
500
|
+
root_trust_level = "CHECKPOINT"
|
|
501
|
+
elif tree_context == "PASS":
|
|
502
|
+
root_trust_level = "ROOT_AND_TREE_SIZE_PINNED"
|
|
503
|
+
elif root_auth == "PASS":
|
|
504
|
+
root_trust_level = "ROOT_BYTES_ONLY"
|
|
505
|
+
else:
|
|
506
|
+
root_trust_level = "NONE"
|
|
476
507
|
# P0-B (audit 2026-07-13): the former `policy_ok is not False` let policy_ok=None (no policy
|
|
477
508
|
# evaluated) through → a crypto-valid, root-pinned receipt looked automation-safe though NO trusted
|
|
478
509
|
# signer was ever authorised. safeForAutomation is now a GLOBAL trust verdict: policy_ok must be True
|
|
@@ -492,24 +523,35 @@ def root_authenticity_summary(result: VerificationResult, *,
|
|
|
492
523
|
blockers.append("CRYPTO_FAILED")
|
|
493
524
|
if root_auth != "PASS":
|
|
494
525
|
blockers.append("ROOT_NOT_AUTHENTICATED")
|
|
526
|
+
if tree_context != "PASS":
|
|
527
|
+
# A-P0-1 §5.4: safeForAutomation requires the ATOMIC (root, tree_size) authentication —
|
|
528
|
+
# root bytes alone cannot detect the 2→3-leaf relabel, so they never authorise automation.
|
|
529
|
+
blockers.append("TREE_CONTEXT_NOT_AUTHENTICATED")
|
|
495
530
|
if policy_ok is None:
|
|
496
531
|
blockers.append("POLICY_NOT_EVALUATED")
|
|
497
532
|
elif policy_ok is False:
|
|
498
533
|
blockers.append("POLICY_FAILED")
|
|
499
|
-
elif requires_identity_overlay:
|
|
534
|
+
elif signer_trusted is not True and not requires_identity_overlay:
|
|
535
|
+
blockers.append("SIGNER_NOT_PINNED") # policy passed but pins no trusted identity (attributes to nobody)
|
|
536
|
+
elif signer_trusted is True and policy_warnings:
|
|
537
|
+
blockers.append("POLICY_WARNINGS_PRESENT") # signer pinned yet the policy still warns (forward-compat)
|
|
538
|
+
if requires_identity_overlay:
|
|
500
539
|
# AP-2 §6.2 (L2 pre-land audit): a RAW template (requiresIdentityOverlay:true) is never automation-safe
|
|
501
540
|
# — reported as its OWN blocker, not SIGNER_NOT_PINNED, which would be factually wrong when the template
|
|
502
|
-
# actually does match a signer (the real reason is the un-cleared template-lifecycle flag).
|
|
541
|
+
# actually does match a signer (the real reason is the un-cleared template-lifecycle flag). Independent
|
|
542
|
+
# of the policy_ok chain since A-P0-2: the eval path now FAILS a raw template (policy:not_template →
|
|
543
|
+
# POLICY_FAILED), and the honest template blocker must still be reported alongside it.
|
|
503
544
|
blockers.append("TEMPLATE_NOT_INSTANTIATED")
|
|
504
|
-
elif signer_trusted is not True:
|
|
505
|
-
blockers.append("SIGNER_NOT_PINNED") # policy passed but pins no trusted identity (attributes to nobody)
|
|
506
|
-
elif policy_warnings:
|
|
507
|
-
blockers.append("POLICY_WARNINGS_PRESENT") # signer pinned yet the policy still warns (forward-compat)
|
|
508
545
|
# AP-2 §6.4 lifecycle: an EXPIRED policy is unsafe to automate on even if it otherwise passed (a stale
|
|
509
546
|
# signer pin the relying party has since rotated away from). Independent of the signer/warning chain so
|
|
510
547
|
# it is reported alongside, never in place of, another reason.
|
|
511
548
|
if policy_expired is True:
|
|
512
549
|
blockers.append("POLICY_EXPIRED")
|
|
550
|
+
# A-P0-2 not-before mirror (Lens-2/3/4/6 review): a policy whose valid_from is in the FUTURE at the
|
|
551
|
+
# real current time is not in force, so a crypto-valid receipt under it is not automation-safe even
|
|
552
|
+
# when historically verified — the symmetric case to POLICY_EXPIRED. Reported at current time.
|
|
553
|
+
if policy_not_yet_valid is True:
|
|
554
|
+
blockers.append("POLICY_NOT_YET_VALID")
|
|
513
555
|
if anchor_ok is False:
|
|
514
556
|
blockers.append("ANCHOR_REQUIRED_FAILED")
|
|
515
557
|
if public_transparency_ok is False:
|
|
@@ -519,7 +561,14 @@ def root_authenticity_summary(result: VerificationResult, *,
|
|
|
519
561
|
return {
|
|
520
562
|
"payloadSignature": _tri("ed25519-signature"),
|
|
521
563
|
"merkleConsistency": _tri("merkle-inclusion"),
|
|
564
|
+
# A-P0-1 §5.3: differentiated statuses. `rootAuthenticity` is kept as a WIRE-COMPAT ALIAS of
|
|
565
|
+
# the root-BYTES verdict (it never asserted more than bytes); the undifferentiated reading
|
|
566
|
+
# of it as full root trust is retired — automation keys off treeContextAuthenticity.
|
|
522
567
|
"rootAuthenticity": root_auth,
|
|
568
|
+
"rootBytesAuthenticity": root_auth,
|
|
569
|
+
"treeContextAuthenticity": tree_context,
|
|
570
|
+
"checkpointAuthenticity": cp_auth,
|
|
571
|
+
"rootTrustLevel": root_trust_level,
|
|
523
572
|
"publicTransparency": "NOT_EVALUATED",
|
|
524
573
|
"safeForAutomation": not blockers,
|
|
525
574
|
"automationBlockers": blockers,
|