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.
Files changed (183) hide show
  1. {proofbundle-3.1.2/src/proofbundle.egg-info → proofbundle-3.2.0}/PKG-INFO +10 -4
  2. {proofbundle-3.1.2 → proofbundle-3.2.0}/README.md +9 -3
  3. {proofbundle-3.1.2 → proofbundle-3.2.0}/pyproject.toml +1 -1
  4. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/__init__.py +43 -2
  5. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_chia_add.py +51 -18
  6. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/bundle.py +56 -7
  7. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/cli.py +348 -13
  8. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/decision.py +5 -1
  9. proofbundle-3.2.0/src/proofbundle/evidence_pack.py +98 -0
  10. proofbundle-3.2.0/src/proofbundle/hashalg.py +177 -0
  11. proofbundle-3.2.0/src/proofbundle/outcome.py +358 -0
  12. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policies/decision-receipt-template-v1.json +1 -0
  13. proofbundle-3.2.0/src/proofbundle/policies/research-preview-v1.json +16 -0
  14. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policies/strict-eval-authenticated-root-template-v1.json +1 -0
  15. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policies/strict-eval-template-v1.json +1 -0
  16. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policies/strict-prereg-template-v1.json +1 -0
  17. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policy.py +302 -18
  18. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/policy_profiles.py +30 -6
  19. proofbundle-3.2.0/src/proofbundle/pqsig.py +114 -0
  20. proofbundle-3.2.0/src/proofbundle/public_transparency.py +201 -0
  21. proofbundle-3.2.0/src/proofbundle/renewal.py +483 -0
  22. proofbundle-3.2.0/src/proofbundle/run_ledger.py +295 -0
  23. proofbundle-3.2.0/src/proofbundle/sdjwt_vc.py +182 -0
  24. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/signature.py +2 -0
  25. proofbundle-3.2.0/src/proofbundle/subject_binding.py +115 -0
  26. proofbundle-3.2.0/src/proofbundle/trust_pack.py +432 -0
  27. proofbundle-3.2.0/src/proofbundle/verification_summary.py +252 -0
  28. {proofbundle-3.1.2 → proofbundle-3.2.0/src/proofbundle.egg-info}/PKG-INFO +10 -4
  29. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/SOURCES.txt +34 -0
  30. proofbundle-3.2.0/tests/test_anchor_longevity_property.py +169 -0
  31. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_chia_add.py +32 -0
  32. proofbundle-3.2.0/tests/test_checkpoint_quorum_property.py +85 -0
  33. proofbundle-3.2.0/tests/test_content_root_property.py +132 -0
  34. proofbundle-3.2.0/tests/test_dsse_adversarial.py +102 -0
  35. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_evalclaim.py +15 -0
  36. proofbundle-3.2.0/tests/test_evidence_pack.py +147 -0
  37. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_fuzz_parsers.py +77 -0
  38. proofbundle-3.2.0/tests/test_hashalg.py +145 -0
  39. proofbundle-3.2.0/tests/test_lens_review_fixes_3_1_3.py +252 -0
  40. proofbundle-3.2.0/tests/test_merkle_consistency_property.py +83 -0
  41. proofbundle-3.2.0/tests/test_official_vectors.py +94 -0
  42. proofbundle-3.2.0/tests/test_outcome_verify.py +281 -0
  43. proofbundle-3.2.0/tests/test_policy_lifecycle_purpose.py +464 -0
  44. proofbundle-3.2.0/tests/test_pqsig.py +126 -0
  45. proofbundle-3.2.0/tests/test_public_transparency.py +193 -0
  46. proofbundle-3.2.0/tests/test_renewal.py +179 -0
  47. proofbundle-3.2.0/tests/test_renewal_policy.py +100 -0
  48. proofbundle-3.2.0/tests/test_renewal_signed.py +214 -0
  49. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_rfc6962_external_vectors.py +20 -5
  50. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_root_authenticity.py +44 -13
  51. proofbundle-3.2.0/tests/test_run_ledger.py +152 -0
  52. proofbundle-3.2.0/tests/test_sdjwt_adversarial.py +134 -0
  53. proofbundle-3.2.0/tests/test_sdjwt_vc.py +222 -0
  54. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_statuslist.py +46 -0
  55. proofbundle-3.2.0/tests/test_subject_binding.py +95 -0
  56. proofbundle-3.2.0/tests/test_tree_context_authenticity.py +329 -0
  57. proofbundle-3.2.0/tests/test_trust_pack.py +430 -0
  58. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_trust_policy.py +4 -2
  59. proofbundle-3.2.0/tests/test_verification_summary.py +167 -0
  60. proofbundle-3.1.2/src/proofbundle/policies/research-preview-v1.json +0 -7
  61. {proofbundle-3.1.2 → proofbundle-3.2.0}/LICENSE +0 -0
  62. {proofbundle-3.1.2 → proofbundle-3.2.0}/setup.cfg +0 -0
  63. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/_inspect_registry.py +0 -0
  64. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/_integration.py +0 -0
  65. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/_strict_json.py +0 -0
  66. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/__init__.py +0 -0
  67. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/_provenance.py +0 -0
  68. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/eee.py +0 -0
  69. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/inspect_ai.py +0 -0
  70. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/lm_eval.py +0 -0
  71. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/promptfoo.py +0 -0
  72. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/adapters/samples.py +0 -0
  73. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors.py +0 -0
  74. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_chia.py +0 -0
  75. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_markovian.py +0 -0
  76. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_ots.py +0 -0
  77. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/anchors_rfc3161.py +0 -0
  78. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/beacon.py +0 -0
  79. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/canonical.py +0 -0
  80. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/checkpoint.py +0 -0
  81. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/demo.py +0 -0
  82. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/dsse.py +0 -0
  83. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/eee_eval_schema.json +0 -0
  84. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/emit.py +0 -0
  85. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/errors.py +0 -0
  86. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/evalclaim.py +0 -0
  87. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/experimental/__init__.py +0 -0
  88. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/experimental/enclave.py +0 -0
  89. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/hf_evals.py +0 -0
  90. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/inspect_hook.py +0 -0
  91. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/intoto.py +0 -0
  92. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/kbjwt.py +0 -0
  93. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/merkle.py +0 -0
  94. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/persample.py +0 -0
  95. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/prereg.py +0 -0
  96. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/py.typed +0 -0
  97. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/pytest_plugin.py +0 -0
  98. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/sdjwt.py +0 -0
  99. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/sdjwt_issue.py +0 -0
  100. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/statuslist.py +0 -0
  101. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle/tlogproof.py +0 -0
  102. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/dependency_links.txt +0 -0
  103. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/entry_points.txt +0 -0
  104. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/requires.txt +0 -0
  105. {proofbundle-3.1.2 → proofbundle-3.2.0}/src/proofbundle.egg-info/top_level.txt +0 -0
  106. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_adapters.py +0 -0
  107. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_adversarial.py +0 -0
  108. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchor_target_trustedtime.py +0 -0
  109. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_chia.py +0 -0
  110. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_chia_claims.py +0 -0
  111. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_generic.py +0 -0
  112. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_markovian.py +0 -0
  113. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_ots.py +0 -0
  114. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_anchors_rfc3161.py +0 -0
  115. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_beacon.py +0 -0
  116. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_branch_base_check.py +0 -0
  117. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_bundle.py +0 -0
  118. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_bundle_robustness.py +0 -0
  119. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_canonical.py +0 -0
  120. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_checkpoint.py +0 -0
  121. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_claims_hygiene.py +0 -0
  122. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cli.py +0 -0
  123. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cli_eval.py +0 -0
  124. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cli_require_anchor.py +0 -0
  125. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_conformance.py +0 -0
  126. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cosignature.py +0 -0
  127. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_cosignature_mldsa.py +0 -0
  128. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_anchors.py +0 -0
  129. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_cli.py +0 -0
  130. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_evidence.py +0 -0
  131. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_fuzz.py +0 -0
  132. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_hardening.py +0 -0
  133. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_policy.py +0 -0
  134. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_schema.py +0 -0
  135. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_validator_api.py +0 -0
  136. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_decision_verify.py +0 -0
  137. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_demo.py +0 -0
  138. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_docs_truth.py +0 -0
  139. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_dup_key_reject.py +0 -0
  140. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_ed25519_semantics.py +0 -0
  141. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_eee.py +0 -0
  142. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_emit.py +0 -0
  143. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_eval_claim_schema.py +0 -0
  144. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_eval_evidence_class.py +0 -0
  145. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_examples.py +0 -0
  146. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_experimental_enclave.py +0 -0
  147. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_fork_pr_secret_isolation.py +0 -0
  148. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_hf_entry_verify_eee_digest.py +0 -0
  149. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_hf_evals.py +0 -0
  150. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_inspect_hook.py +0 -0
  151. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto.py +0 -0
  152. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_claims_hygiene.py +0 -0
  153. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_content_root_migration.py +0 -0
  154. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_dsse.py +0 -0
  155. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_eval_result.py +0 -0
  156. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_examples.py +0 -0
  157. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_spec_diff.py +0 -0
  158. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_intoto_svr.py +0 -0
  159. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_kbjwt.py +0 -0
  160. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_merkle.py +0 -0
  161. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_merkle_property.py +0 -0
  162. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_ok_semantics.py +0 -0
  163. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_oversized_int_guard.py +0 -0
  164. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_persample.py +0 -0
  165. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_pinned_key_validation.py +0 -0
  166. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_policy_explain_lint.py +0 -0
  167. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_policy_profiles.py +0 -0
  168. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_policy_templates.py +0 -0
  169. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_predicate_type_enforcement.py +0 -0
  170. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_prereg.py +0 -0
  171. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_promptfoo.py +0 -0
  172. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_provenance.py +0 -0
  173. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_pytest_plugin.py +0 -0
  174. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_rekor_interop.py +0 -0
  175. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_release_integrity_gate.py +0 -0
  176. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_schema.py +0 -0
  177. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_sdjwt_duplicate_cnf.py +0 -0
  178. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_sdjwt_issue.py +0 -0
  179. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_sdjwt_reference.py +0 -0
  180. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_sdjwt_verify_binding.py +0 -0
  181. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_signature.py +0 -0
  182. {proofbundle-3.1.2 → proofbundle-3.2.0}/tests/test_tlogproof.py +0 -0
  183. {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.1.2
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 --expected-root <b64>
131
- # root_authenticity.safeForAutomation: true only when the policy pins a trusted signer AND the
132
- # root is authenticated (else false, with automationBlockers naming every reason)
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 --expected-root <b64>
77
- # root_authenticity.safeForAutomation: true only when the policy pins a trusted signer AND the
78
- # root is authenticated (else false, with automationBlockers naming every reason)
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.1.2"
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.1.2"
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-11"
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 typing import Optional
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
- prev_root = _rpc("data_layer", "get_root", {"id": store_id}).get("hash")
141
- changelist = [{"action": "insert", "key": canonical_root_hex, "value": value_hex}]
142
- already = False
143
- try:
144
- _rpc("data_layer", "batch_update",
145
- {"id": store_id, "changelist": changelist, "fee": fee, "submit_on_chain": True})
146
- except ChiaRpcError as exc:
147
- if "already present" in str(exc).lower():
148
- already = True # idempotent: the digest is already anchored
149
- else:
150
- raise # any other failure → clean abort, nothing written
151
- if wait and not already:
152
- _wait_confirmed(store_id, prev_root)
153
- return export_anchor(store_id, canonical_root=canonical_root,
154
- target=target, network=network, value=value_hex)
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) -> dict:
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,