consequa-x 2.1.1__tar.gz → 2.1.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.
Files changed (232) hide show
  1. {consequa_x-2.1.1 → consequa_x-2.1.2}/CHANGELOG.md +16 -1
  2. {consequa_x-2.1.1 → consequa_x-2.1.2}/PKG-INFO +1 -1
  3. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/quickstart.md +2 -2
  4. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/release-notes.md +15 -0
  5. {consequa_x-2.1.1 → consequa_x-2.1.2}/pyproject.toml +1 -1
  6. consequa_x-2.1.2/scripts/audit_external_validation_artifacts.py +458 -0
  7. consequa_x-2.1.2/scripts/audit_upstream_car_artifacts.py +616 -0
  8. consequa_x-2.1.2/scripts/audit_v2_migration_conformance.py +510 -0
  9. consequa_x-2.1.2/scripts/build_blinded_trace_review.py +401 -0
  10. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/build_public_docs.py +1 -1
  11. consequa_x-2.1.2/scripts/plot_external_validation.py +181 -0
  12. consequa_x-2.1.2/scripts/plot_upstream_car_validation.py +440 -0
  13. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/__init__.py +1 -1
  14. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/cli.py +2 -1
  15. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/explanation.py +2 -5
  16. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x.egg-info/PKG-INFO +1 -1
  17. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x.egg-info/SOURCES.txt +8 -0
  18. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_cli.py +11 -0
  19. consequa_x-2.1.2/tests/test_external_validation_artifact_audit.py +363 -0
  20. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_integration.py +3 -3
  21. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_policy_runner.py +1 -1
  22. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_public_integration.py +1 -1
  23. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_site_demo.py +10 -0
  24. consequa_x-2.1.2/tests/test_snapshot_integrity.py +178 -0
  25. consequa_x-2.1.2/tests/test_v2_migration_artifact_audit.py +433 -0
  26. consequa_x-2.1.1/tests/test_snapshot_integrity.py +0 -60
  27. {consequa_x-2.1.1 → consequa_x-2.1.2}/CONTRIBUTING.md +0 -0
  28. {consequa_x-2.1.1 → consequa_x-2.1.2}/LICENSE +0 -0
  29. {consequa_x-2.1.1 → consequa_x-2.1.2}/MANIFEST.in +0 -0
  30. {consequa_x-2.1.1 → consequa_x-2.1.2}/README.md +0 -0
  31. {consequa_x-2.1.1 → consequa_x-2.1.2}/SECURITY.md +0 -0
  32. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/application-integration.md +0 -0
  33. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/attribution-calibration.md +0 -0
  34. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/build-contract.md +0 -0
  35. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/car-reproduction-plan.md +0 -0
  36. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/completion-plan.md +0 -0
  37. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/decision-contrastive.md +0 -0
  38. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/decision-replay.md +0 -0
  39. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/decision-shapley.md +0 -0
  40. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/design-2.0.md +0 -0
  41. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/design.md +0 -0
  42. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/framework-integration.md +0 -0
  43. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/graph-witnesses.md +0 -0
  44. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/interaction-sampling.md +0 -0
  45. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/interval-followup-plan.md +0 -0
  46. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/launch-readiness.md +0 -0
  47. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/local-pypi-setup.md +0 -0
  48. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/matched-trace-judge.md +0 -0
  49. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/migrating-to-2.0.md +0 -0
  50. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/offline-reports.md +0 -0
  51. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/opentelemetry.md +0 -0
  52. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/policy-evaluation.md +0 -0
  53. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/policy-replay.md +0 -0
  54. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/product-roadmap.md +0 -0
  55. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/refund-interactions.md +0 -0
  56. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/release-readiness.md +0 -0
  57. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/research-roadmap.md +0 -0
  58. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/stable-api.md +0 -0
  59. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/studies/matched-local-2026-10-18.json +0 -0
  60. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/studies/refund-family-candidates-v1.json +0 -0
  61. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/study-manifests.md +0 -0
  62. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/study-plan.md +0 -0
  63. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/tau-benchmark-validation.md +0 -0
  64. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/upstream-car-comparison.md +0 -0
  65. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/v1.1-release.md +0 -0
  66. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/v2-migration-conformance.md +0 -0
  67. {consequa_x-2.1.1 → consequa_x-2.1.2}/docs/variable-length-game-plan.md +0 -0
  68. {consequa_x-2.1.1 → consequa_x-2.1.2}/examples/agent_loop_sqlite.py +0 -0
  69. {consequa_x-2.1.1 → consequa_x-2.1.2}/examples/artifact_delivery.py +0 -0
  70. {consequa_x-2.1.1 → consequa_x-2.1.2}/examples/decision_shapley_fixture.py +0 -0
  71. {consequa_x-2.1.1 → consequa_x-2.1.2}/examples/report_quickstart.py +0 -0
  72. {consequa_x-2.1.1 → consequa_x-2.1.2}/examples/two_part_outcome.py +0 -0
  73. {consequa_x-2.1.1 → consequa_x-2.1.2}/requirements-dev.lock +0 -0
  74. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/analyze_matched_local_study.py +0 -0
  75. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/audit_interaction_sampling.py +0 -0
  76. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/audit_interval_comparison.py +0 -0
  77. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/audit_refund_interactions.py +0 -0
  78. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/audit_release_artifacts.py +0 -0
  79. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/audit_review_revision.py +0 -0
  80. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/build_matched_study_figures.py +0 -0
  81. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/build_site_demo.py +0 -0
  82. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/check_installed_release.py +0 -0
  83. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/check_live_integrations.py +0 -0
  84. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/configure_pypi_auth.py +0 -0
  85. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/draw_paper_diagrams.py +0 -0
  86. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/export_render_site.py +0 -0
  87. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/finalize_interval_artifacts.py +0 -0
  88. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/finalize_matched_local_study.py +0 -0
  89. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/finalize_refund_artifacts.py +0 -0
  90. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/plot_interval_comparison.py +0 -0
  91. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/plot_paper_results.py +0 -0
  92. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/publish_alpha.py +0 -0
  93. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/publish_release.py +0 -0
  94. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/refresh_sample_report.py +0 -0
  95. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/run_interaction_sampling.py +0 -0
  96. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/run_matched_local_study.py +0 -0
  97. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/run_matched_trace_judge.py +0 -0
  98. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/run_tau_benchmark_validation.py +0 -0
  99. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/run_upstream_car_study.py +0 -0
  100. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/run_v2_migration_conformance.py +0 -0
  101. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/summarize_interaction_sampling.py +0 -0
  102. {consequa_x-2.1.1 → consequa_x-2.1.2}/scripts/summarize_matched_execution.py +0 -0
  103. {consequa_x-2.1.1 → consequa_x-2.1.2}/setup.cfg +0 -0
  104. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/__main__.py +0 -0
  105. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/adapters/__init__.py +0 -0
  106. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/adapters/ollama.py +0 -0
  107. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/attribution.py +0 -0
  108. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/__init__.py +0 -0
  109. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/assessment_metrics.py +0 -0
  110. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/calibration.py +0 -0
  111. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/calibration_export.py +0 -0
  112. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/calibration_mechanisms.py +0 -0
  113. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/decision_coalition.py +0 -0
  114. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/decision_contrastive.py +0 -0
  115. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/decision_replay.py +0 -0
  116. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/decision_shapley.py +0 -0
  117. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/evaluation.py +0 -0
  118. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/interaction_export.py +0 -0
  119. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/interaction_mechanisms.py +0 -0
  120. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/interaction_sampling.py +0 -0
  121. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/interval_comparison.py +0 -0
  122. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/intervals.py +0 -0
  123. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/payments.py +0 -0
  124. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/policy_experiment.py +0 -0
  125. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/policy_runner.py +0 -0
  126. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/runner.py +0 -0
  127. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/study.py +0 -0
  128. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/targets.py +0 -0
  129. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/benchmark/templates.py +0 -0
  130. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/capture.py +0 -0
  131. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/compare.py +0 -0
  132. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/composite.py +0 -0
  133. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/errors.py +0 -0
  134. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/graph.py +0 -0
  135. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/guard.py +0 -0
  136. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/integrations/__init__.py +0 -0
  137. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/integrations/claude_agent_sdk.py +0 -0
  138. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/integrations/langchain.py +0 -0
  139. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/integrations/openai_agents.py +0 -0
  140. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/mcp_capture.py +0 -0
  141. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/migrate.py +0 -0
  142. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/models.py +0 -0
  143. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/otel.py +0 -0
  144. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/policies.py +0 -0
  145. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/py.typed +0 -0
  146. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/readback.py +0 -0
  147. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/record_verifier.py +0 -0
  148. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/report.py +0 -0
  149. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/research.py +0 -0
  150. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/schema.py +0 -0
  151. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/schemas/trace-v1.0.json +0 -0
  152. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/store.py +0 -0
  153. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/summary.py +0 -0
  154. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x/verification.py +0 -0
  155. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x.egg-info/dependency_links.txt +0 -0
  156. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x.egg-info/entry_points.txt +0 -0
  157. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x.egg-info/requires.txt +0 -0
  158. {consequa_x-2.1.1 → consequa_x-2.1.2}/src/consequa_x.egg-info/top_level.txt +0 -0
  159. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/fixtures/v0.1/README.md +0 -0
  160. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/fixtures/v0.1/record-timeout-after-commit.json +0 -0
  161. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/fixtures/v0.1/refund-another-actor.json +0 -0
  162. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/fixtures/v0.1/refund-stale-readback.json +0 -0
  163. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/fixtures/v0.1/refund-timeout-after-commit.explanation.json +0 -0
  164. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/fixtures/v0.1/refund-timeout-after-commit.json +0 -0
  165. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_agent_loop_example.py +0 -0
  166. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_assessment_metrics.py +0 -0
  167. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_async_capture.py +0 -0
  168. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_attribution.py +0 -0
  169. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_calibration.py +0 -0
  170. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_calibration_cli.py +0 -0
  171. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_calibration_mechanisms.py +0 -0
  172. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_capture.py +0 -0
  173. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_compare_verify_redact.py +0 -0
  174. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_composite.py +0 -0
  175. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_decision_coalition.py +0 -0
  176. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_decision_contrastive.py +0 -0
  177. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_decision_contrastive_cli.py +0 -0
  178. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_decision_replay.py +0 -0
  179. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_decision_shapley.py +0 -0
  180. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_decision_shapley_cli.py +0 -0
  181. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_docs_examples.py +0 -0
  182. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_errors.py +0 -0
  183. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_graph.py +0 -0
  184. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_graph_identity.py +0 -0
  185. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_installed_release_check.py +0 -0
  186. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_integrations.py +0 -0
  187. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_interaction_sampling.py +0 -0
  188. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_interaction_sampling_cli.py +0 -0
  189. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_interaction_workers.py +0 -0
  190. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_interval_comparison.py +0 -0
  191. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_interval_replay.py +0 -0
  192. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_intervals.py +0 -0
  193. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_matched_baseline_audit.py +0 -0
  194. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_matched_execution_summary.py +0 -0
  195. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_matched_figure_builder.py +0 -0
  196. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_matched_local_study.py +0 -0
  197. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_matched_study_finalizer.py +0 -0
  198. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_matched_trace_judge.py +0 -0
  199. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_mcp_capture.py +0 -0
  200. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_migrate.py +0 -0
  201. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_model_eval_integration.py +0 -0
  202. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_models.py +0 -0
  203. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_multi_action.py +0 -0
  204. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_ollama.py +0 -0
  205. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_otel.py +0 -0
  206. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_payments.py +0 -0
  207. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_policies.py +0 -0
  208. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_policy_evaluation.py +0 -0
  209. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_policy_experiment.py +0 -0
  210. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_policy_experiment_integration.py +0 -0
  211. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_prompt_profile_cli.py +0 -0
  212. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_readback_guard.py +0 -0
  213. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_record_verifier.py +0 -0
  214. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_refund_interactions.py +0 -0
  215. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_refund_interactions_cli.py +0 -0
  216. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_release_helpers.py +0 -0
  217. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_release_inventory.py +0 -0
  218. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_render_site_export.py +0 -0
  219. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_replay_targets.py +0 -0
  220. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_report.py +0 -0
  221. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_report_cli.py +0 -0
  222. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_research_integration.py +0 -0
  223. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_schema.py +0 -0
  224. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_stable_release.py +0 -0
  225. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_study.py +0 -0
  226. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_summary_store.py +0 -0
  227. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_task_templates.py +0 -0
  228. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_tau_benchmark_validation.py +0 -0
  229. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_two_part_example.py +0 -0
  230. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_upstream_car_study.py +0 -0
  231. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_v2_migration_conformance.py +0 -0
  232. {consequa_x-2.1.1 → consequa_x-2.1.2}/tests/test_verification.py +0 -0
@@ -1,8 +1,23 @@
1
1
  # Changes
2
2
 
3
+ ## 2.1.2 - 20 October 2026
4
+
5
+ A maintenance release; the API, exported records and trace schema are
6
+ unchanged.
7
+
8
+ - `ActionExplanation.diagnosis`, the one-line display label, is always the
9
+ diagnosis codes joined with ` / `. A timeout after commit no longer shows the
10
+ 1.x label `BELIEF / RESPONSE DIVERGENCE`: `consequa-x demo` and its README now
11
+ print `ASSESSMENT_DIVERGENCE / ACKNOWLEDGEMENT_EFFECT_DIVERGENCE`, the codes
12
+ that explanations and reports already carried.
13
+
3
14
  ## 2.1.1 - 19 October 2026
4
15
 
5
- A maintenance release; the API and trace schema are unchanged.
16
+ A maintenance release; the API and trace schema are unchanged. Published to
17
+ PyPI with registry hashes verified; see the
18
+ [release record](output/release-2.1.1-2026-10-19/README.md). Installed checks
19
+ passed on Windows Python 3.11 to 3.15, on one Alpine Linux QEMU guest with
20
+ Python 3.14 and from a fresh PyPI install. macOS has never been tested.
6
21
 
7
22
  - The project's name is written ConseqaX: README, documentation, report title
8
23
  and footer, the trace schema's title and command-line text. The package,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: consequa-x
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: Evidence-aware explanation of consequential agent actions
5
5
  Author: Devrajsinh Jhala
6
6
  License-Expression: MIT
@@ -1,10 +1,10 @@
1
1
  # Five-minute offline quickstart
2
2
 
3
3
  Use Python 3.11 or newer. No model, API key, provider account or runtime dependency
4
- is required. Version 2.1.1 is public on PyPI. Traces use schema 1.0.
4
+ is required. Version 2.1.2 is public on PyPI. Traces use schema 1.0.
5
5
 
6
6
  ```console
7
- python -m pip install consequa-x==2.1.1
7
+ python -m pip install consequa-x==2.1.2
8
8
  consequa-x --version
9
9
  ```
10
10
 
@@ -1,5 +1,14 @@
1
1
  # Release notes
2
2
 
3
+ ## 2.1.2 — 20 October 2026
4
+
5
+ - The demo's timeout-after-commit case now prints its diagnosis codes,
6
+ ASSESSMENT_DIVERGENCE and ACKNOWLEDGEMENT_EFFECT_DIVERGENCE, instead of an
7
+ old label from version 1. Reports and saved explanations already showed these
8
+ codes.
9
+ - Nothing else changed: saved traces, explanations and code written for 2.1
10
+ work as before.
11
+
3
12
  ## 2.1.1 — 19 October 2026
4
13
 
5
14
  - The project is now written ConseqaX. You still install it with
@@ -11,6 +20,12 @@
11
20
  - A new example, two_part_outcome.py, checks a refund and a ticket closure
12
21
  as one outcome.
13
22
 
23
+ Both files are published on PyPI with their registry hashes and sizes verified,
24
+ and a fresh install from PyPI passed the installed checks. Installed checks of
25
+ the same wheel passed on Windows with Python 3.11.9, 3.12.10, 3.13.16, 3.14.8
26
+ and 3.15.0, and on Alpine Linux 3.24.2 with Python 3.14.8 in a local QEMU guest.
27
+ The Linux result covers one emulated guest; macOS has not been tested.
28
+
14
29
  ## 2.1.0 — 19 October 2026
15
30
 
16
31
  An additive release with the features 2.0.0 left out. Saved traces and code
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "consequa-x"
7
- version = "2.1.1"
7
+ version = "2.1.2"
8
8
  description = "Evidence-aware explanation of consequential agent actions"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1,458 @@
1
+ """Portable integrity/accounting audit; not semantic or independent human validation.
2
+
3
+ Standard library only. Never imports the framework, collectors or upstream tools.
4
+ Historical absolute inputs require explicit --relocate OLD=NEW mappings. Local
5
+ historical source bytes can be checked against their pinned Git objects. No
6
+ protocol is rewritten, and missing bindings are fatal, including dependency files.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ from collections import Counter, defaultdict
12
+ from datetime import datetime, timezone
13
+ from hashlib import sha256
14
+ import json
15
+ from pathlib import Path, PurePosixPath
16
+ import re
17
+ import subprocess
18
+ import sys
19
+ import zipfile
20
+
21
+
22
+ JUDGE = "output/matched-trace-judge-2026-10-18"
23
+ TAU = "output/tau3-state-transfer-2026-10-18"
24
+ ANALYSIS = "output/matched-trace-judge-analysis-2026-10-18"
25
+ JUDGE_SHA = "4439983d1f861eaffaffbc4140750a8a1740653baa8eb7037ae5df600ca074ae"
26
+ TAU_SHA = "cf3f80e687b4a5094e54748964d46af032eb47406bd0d506fa15b33f20ac0d29"
27
+ SCOPE = "Portable integrity/accounting audit, not independent semantic or human validation"
28
+
29
+
30
+ def require(condition, message):
31
+ if not condition:
32
+ raise ValueError(message)
33
+
34
+
35
+ def encoded(value):
36
+ return (json.dumps(value, sort_keys=True, ensure_ascii=False, allow_nan=False,
37
+ separators=(",", ":")) + "\n").encode("utf-8")
38
+
39
+
40
+ def digest(blob):
41
+ return sha256(blob).hexdigest()
42
+
43
+
44
+ def pairs(values):
45
+ output = {}
46
+ for key, value in values:
47
+ require(key not in output, "Duplicate JSON object key")
48
+ output[key] = value
49
+ return output
50
+
51
+
52
+ def decode(blob):
53
+ return json.loads(blob, object_pairs_hook=pairs,
54
+ parse_constant=lambda _: (_ for _ in ()).throw(ValueError("Nonfinite JSON")))
55
+
56
+
57
+ def same(left, right, message):
58
+ require(encoded(left) == encoded(right), message)
59
+
60
+
61
+ def relative_name(name):
62
+ require(type(name) is str and bool(name), "Artifact path must be a string")
63
+ path = PurePosixPath(name)
64
+ require(bool(path.parts) and not path.is_absolute() and path.as_posix() == name and ".." not in path.parts
65
+ and "\\" not in name and ":" not in name, "Artifact path escape or noncanonical path")
66
+ return path
67
+
68
+
69
+ def safe_path(base, name):
70
+ relative_name(name)
71
+ path = Path(base) / name
72
+ require(path.resolve().is_relative_to(Path(base).resolve()) and not path.is_symlink(),
73
+ "Artifact path leaves declared root")
74
+ return path
75
+
76
+
77
+ class Audit:
78
+ def __init__(self, root, relocations=()):
79
+ self.root = Path(root).resolve()
80
+ self.maps = []
81
+ self.checked = 0
82
+ self.git_blobs = 0
83
+ for entry in relocations:
84
+ old, separator, new = entry.partition("=")
85
+ require(separator and old and new, "Relocation must be OLD=NEW")
86
+ old = old.replace("\\", "/").rstrip("/")
87
+ require(".." not in PurePosixPath(old).parts, "Invalid historical relocation prefix")
88
+ require(not any(prefix.casefold() == old.casefold() for prefix, _ in self.maps),
89
+ "Duplicate relocation prefix")
90
+ self.maps.append((old, Path(new).resolve()))
91
+ self.maps.sort(key=lambda item: len(item[0]), reverse=True)
92
+
93
+ def read(self, path, expected=None):
94
+ require(not Path(path).is_symlink(), "Symlink input refused")
95
+ blob = Path(path).read_bytes()
96
+ if expected is not None:
97
+ require(re.fullmatch(r"[0-9a-f]{64}", expected) is not None, "Invalid SHA-256 declaration")
98
+ require(digest(blob) == expected, "Bound file bytes changed")
99
+ self.checked += 1
100
+ return blob
101
+
102
+ def json(self, path, expected=None):
103
+ return decode(self.read(path, expected))
104
+
105
+ def relocate(self, historical):
106
+ historical = historical.replace("\\", "/")
107
+ for prefix, destination in self.maps:
108
+ if historical.casefold().startswith(prefix.casefold() + "/"):
109
+ return safe_path(destination, historical[len(prefix) + 1:])
110
+ if historical.casefold() == prefix.casefold():
111
+ return destination
112
+ raise ValueError("Missing explicit relocation for a historical absolute binding")
113
+
114
+ def historical_code(self, name, expected, revision):
115
+ path = safe_path(self.root, name)
116
+ if path.is_file() and digest(path.read_bytes()) == expected:
117
+ self.checked += 1
118
+ return
119
+ require(re.fullmatch(r"[0-9a-f]{40}", revision) is not None, "Invalid pinned source revision")
120
+ result = subprocess.run(["git", "-C", str(self.root), "show", f"{revision}:{name}"],
121
+ capture_output=True, check=True)
122
+ require(digest(result.stdout) == expected, "Pinned Git source bytes differ from frozen hash")
123
+ self.git_blobs += 1
124
+
125
+ def absolute_bindings(self, bindings):
126
+ for name, expected in bindings.items():
127
+ self.read(self.relocate(name), expected)
128
+
129
+
130
+ def sealed_protocol(audit, directory, expected):
131
+ blob = audit.read(directory / "protocol.json", expected)
132
+ seal = audit.json(directory / "protocol.sha256.json")
133
+ require(seal.get("sha256") == digest(blob), "Original protocol seal changed")
134
+ return decode(blob), digest(blob)
135
+
136
+
137
+ def inventory(directory, pattern, expected):
138
+ actual = {path.relative_to(directory).as_posix() for path in directory.glob(pattern) if path.is_file()}
139
+ same(sorted(actual), sorted(expected), "Relative artifact inventory has omitted or extra slots/files")
140
+
141
+
142
+ def parse_prediction(envelope):
143
+ require(envelope.get("done") is True and envelope.get("done_reason") != "length",
144
+ "Judge response did not complete")
145
+ message = envelope.get("message", {})
146
+ require(message.get("role") == "assistant" and not message.get("tool_calls"), "Invalid judge message")
147
+ value = decode(message["content"])
148
+ require(set(value) == {"state", "action_linked", "origin"}, "Invalid judge output schema")
149
+ mapping = {"state": {"satisfied": True, "violated": False, "unknown": None},
150
+ "action_linked": {"satisfied": True, "not_satisfied": False, "unknown": None},
151
+ "origin": {"confirmed": "confirmed", "other_actor": "other_actor", "unknown": "unknown"}}
152
+ require(all(type(value[key]) is str and value[key] in table for key, table in mapping.items()),
153
+ "Invalid judge target enum")
154
+ return {key: table[value[key]] for key, table in mapping.items()}
155
+
156
+
157
+ def ratio(numerator, denominator):
158
+ return {"numerator": numerator, "denominator": denominator,
159
+ "value": numerator / denominator if denominator else None}
160
+
161
+
162
+ def judge_summary(entries, receipts, context):
163
+ """Recount declared labels only: never infer a semantic verdict from evidence."""
164
+ result = {"selected_planned": len(entries), "available_factual_inputs": len(entries),
165
+ "judge_status": {"completed": len(entries)}, "attempted_chat_calls": len(entries), "methods": {}}
166
+ by_id = {row["slot_id"]: row for row in receipts}
167
+ for method in ("local_trace_judge", "independent_public_reference", "consequa_state_origin"):
168
+ targets = {}
169
+ for target in ("state", "action_linked", "origin"):
170
+ counts = Counter()
171
+ for entry in entries:
172
+ key = entry["slot"]["slot_id"]
173
+ evaluation = context[key]
174
+ prediction = (by_id[key]["prediction"][target] if method == "local_trace_judge"
175
+ else evaluation["baselines"][method][target])
176
+ gold = evaluation["gold"][target]
177
+ reference = evaluation["baselines"]["independent_public_reference"][target]
178
+ known = (lambda x: x != "unknown") if target == "origin" else (lambda x: x is not None)
179
+ definite, assessable = known(prediction), known(gold)
180
+ qualified = definite and known(reference) and prediction == reference
181
+ counts["all_definite_outputs"] += definite
182
+ counts["all_evidence_supported_claims"] += qualified
183
+ if not assessable:
184
+ counts["claims_with_unassessable_gold"] += definite
185
+ continue
186
+ counts["gold_assessable_outputs"] += 1
187
+ if not definite:
188
+ continue
189
+ counts["claims"] += 1
190
+ counts["correct_claims"] += prediction == gold
191
+ counts["wrong_claims"] += prediction != gold
192
+ counts["evidence_supported_claims"] += qualified
193
+ counts["unsupported_correct_guesses"] += prediction == gold and not qualified
194
+ if target != "origin":
195
+ counts["false_positive"] += prediction is True and gold is False
196
+ counts["false_negative"] += prediction is False and gold is True
197
+ keys = ("gold_assessable_outputs", "claims", "correct_claims", "wrong_claims",
198
+ "evidence_supported_claims", "all_definite_outputs", "claims_with_unassessable_gold",
199
+ "all_evidence_supported_claims", "unsupported_correct_guesses", "false_positive", "false_negative")
200
+ metric = {key: counts[key] for key in keys}
201
+ metric.update(completed_outputs=len(entries), abstentions=counts["gold_assessable_outputs"] - counts["claims"])
202
+ metric.update(coverage=ratio(counts["claims"], counts["gold_assessable_outputs"]),
203
+ claim_error=ratio(counts["wrong_claims"], counts["claims"]),
204
+ correct_yield=ratio(counts["correct_claims"], counts["gold_assessable_outputs"]),
205
+ selected_planned_correct_yield=ratio(counts["correct_claims"], len(entries)),
206
+ selected_available_input_correct_yield=ratio(counts["correct_claims"], len(entries)))
207
+ targets[target] = metric
208
+ result["methods"][method] = targets
209
+ return result
210
+
211
+
212
+ def audit_judge(audit, directory, analysis=None, *, expected_slots=84, expected_protocol_hash=JUDGE_SHA):
213
+ directory = Path(directory)
214
+ require(not (directory / "runner.lock").exists(), "Judge collection is active")
215
+ protocol, protocol_hash = sealed_protocol(audit, directory, expected_protocol_hash)
216
+ entries = protocol["schedule"]
217
+ require(len(entries) == protocol["selected_planned"] == protocol["available_inputs"] == expected_slots,
218
+ "Judge planned inventory changed")
219
+ for name, expected in protocol["code_sha256"].items():
220
+ audit.historical_code(name, expected, protocol["source_revision"])
221
+ audit.absolute_bindings(protocol["external_source_sha256"])
222
+ context = audit.json(directory / "evaluation-context.json", protocol["evaluation_context_sha256"])
223
+ slots = {entry["slot"]["slot_id"]: entry for entry in entries}
224
+ require(len(slots) == expected_slots, "Duplicate judge slot")
225
+ same(sorted(context), sorted(slots), "Evaluation context inventory changed")
226
+ lines = audit.read(directory / "request-journal.jsonl").splitlines(keepends=True)
227
+ previous, events = None, []
228
+ for index, line in enumerate(lines):
229
+ require(line.endswith(b"\n"), "Torn judge journal")
230
+ row = decode(line)
231
+ claimed = row.pop("sha256")
232
+ require(row["index"] == index and row["previous_sha256"] == previous
233
+ and digest(encoded(row)) == claimed, "Judge journal chain changed")
234
+ previous = claimed
235
+ events.append(row)
236
+ require(len(events) == 2 + 4 * expected_slots and events[0]["event"] == "run_started"
237
+ and events[-1]["event"] == "run_stopped"
238
+ and events[-1]["stopped_reason"] == "all_never_started_slots_processed", "Judge closure/inventory changed")
239
+ receipts, expected_calls = [], set()
240
+ for offset, entry in enumerate(entries):
241
+ slot = entry["slot"]["slot_id"]
242
+ relative_name(slot)
243
+ require(entry["input_status"] == "available", "Unavailable judge input in completed audit")
244
+ value = audit.json(safe_path(directory, entry["input_file"]), entry["input_sha256"])
245
+ group = events[1 + offset * 4:5 + offset * 4]
246
+ same([row["event"] for row in group], ["judge_started", "chat_started", "chat_received", "judge_finished"],
247
+ "Judge slot event order changed")
248
+ require(all(row["slot_id"] == slot for row in group), "Judge journal slot order changed")
249
+ started = audit.json(safe_path(directory, f"judgments/{slot}/started.json"), group[0]["started_sha256"])
250
+ same(started["entry"], entry, "Judge start schedule binding changed")
251
+ require(started["protocol_sha256"] == protocol_hash, "Judge start protocol binding changed")
252
+ request_event, response_event = group[1:3]
253
+ require(request_event["call_id"] == response_event["call_id"] == slot + "-call-00", "Judge call identity changed")
254
+ require(response_event["status"] == 200 and response_event["truncated"] is False, "Judge raw response failed")
255
+ request = audit.json(safe_path(directory, request_event["request_file"]), request_event["request_sha256"])
256
+ expected = {"model": protocol["model"], "stream": False, "think": False, "format": protocol["output_schema"],
257
+ "messages": [{"role": "system", "content": protocol["prompt"]},
258
+ {"role": "user", "content": encoded(value).decode().rstrip("\n")}],
259
+ "options": {"seed": entry["judge_seed"], "temperature": protocol["settings"]["temperature"],
260
+ "num_predict": protocol["settings"]["num_predict"]}}
261
+ same(request, expected, "Judge request differs from frozen public input/settings")
262
+ envelope = audit.json(safe_path(directory, response_event["response_file"]), response_event["response_sha256"])
263
+ receipt = audit.json(safe_path(directory, f"judgments/{slot}/receipt.json"), group[3]["receipt_sha256"])
264
+ require(receipt["status"] == "completed" and receipt["attempted_calls"] == 1
265
+ and receipt["slot_id"] == slot, "Judge completed receipt changed")
266
+ same(receipt["prediction"], parse_prediction(envelope), "Judge prediction differs from raw response")
267
+ receipts.append(receipt)
268
+ expected_calls.update((request_event["request_file"], response_event["response_file"]))
269
+ inventory(directory, "calls/**/*", expected_calls)
270
+ inventory(directory, "inputs/**/*", {entry["input_file"] for entry in entries})
271
+ inventory(directory, "judgments/**/*", {f"judgments/{slot}/{name}.json" for slot in slots for name in ("started", "receipt")})
272
+ summary = judge_summary(entries, receipts, context)
273
+ if analysis is not None:
274
+ verify_manifest(audit, Path(analysis) / "manifest.json")
275
+ saved = audit.json(Path(analysis) / "analysis.json")
276
+ require(saved["final"] is True and saved["collection_state"] == "completed"
277
+ and saved["protocol_sha256"] == protocol_hash and saved["attempted_calls_including_partial"] == expected_slots,
278
+ "Judge analysis closure changed")
279
+ require(saved["journal_sha256"] == digest((directory / "request-journal.jsonl").read_bytes()), "Analysis journal binding changed")
280
+ same(saved["summary"], summary, "Judge analysis fractions changed")
281
+ same(saved["receipts"], receipts, "Judge analysis receipt inventory changed")
282
+ for axes, buckets in saved["strata"].items():
283
+ expected_buckets = defaultdict(list)
284
+ for entry in entries:
285
+ key = json.dumps({axis: entry["slot"][axis] for axis in axes.split("/")}, sort_keys=True)
286
+ expected_buckets[key].append(entry)
287
+ same(sorted(buckets), sorted(expected_buckets), "Judge stratum inventory changed")
288
+ for key, subset in expected_buckets.items():
289
+ same(buckets[key], judge_summary(subset, receipts, context), "Judge stratum fractions changed")
290
+ return {"completed": len(receipts), "attempted_calls": len(receipts), "journal_events": len(events),
291
+ "protocol_sha256": protocol_hash, "source_revision": protocol["source_revision"], "summary": summary}
292
+
293
+
294
+ def audit_tau(audit, directory, *, expected_cells=656, expected_captures=900, expected_protocol_hash=TAU_SHA):
295
+ directory = Path(directory)
296
+ require(not (directory / "runner.lock").exists(), "Tau collection is active")
297
+ protocol, protocol_hash = sealed_protocol(audit, directory, expected_protocol_hash)
298
+ summary = audit.json(directory / "summary.json")
299
+ require(len(protocol["schedule"]) == summary["planned"] == expected_cells and summary["model_calls"] == 0,
300
+ "Tau planned/model inventory changed")
301
+ require(summary["upstream_commit"] == protocol["upstream_commit"], "Tau upstream revision changed")
302
+ local = {"runner": "scripts/run_tau_benchmark_validation.py", "protocol": "docs/tau-benchmark-validation.md",
303
+ "tests": "tests/test_tau_benchmark_validation.py"}
304
+ for name, expected in protocol["local_hashes"].items():
305
+ path = "src/consequa_x/" + name[8:] if name.startswith("runtime/") else local[name]
306
+ audit.historical_code(path, expected, protocol["source_revision"])
307
+ source = audit.relocate(protocol["source"])
308
+ for name, expected in protocol["upstream_hashes"].items():
309
+ audit.read(safe_path(source, name), expected)
310
+ audit.absolute_bindings(protocol["loaded_dependency_hashes"])
311
+ expected_files, expected_starts = set(), set()
312
+ counts, actions, captures, starts = Counter(), Counter(), 0, {}
313
+ native_records, native_executed = 0, 0
314
+ for index, slot in enumerate(protocol["schedule"]):
315
+ require(slot["slot"] == index, "Tau slot order changed")
316
+ name, start_name = f"episodes/episode_{index:04d}.json", f"started/episode_{index:04d}.json"
317
+ expected_files.add(name)
318
+ expected_starts.add(start_name)
319
+ row = audit.json(safe_path(directory, name), summary["files"][name])
320
+ started_bytes = audit.read(safe_path(directory, start_name))
321
+ started = decode(started_bytes)
322
+ starts[start_name] = digest(started_bytes)
323
+ same({key: started[key] for key in slot}, slot, "Tau start schedule/hash binding changed")
324
+ for key in ("slot", "domain", "task_id", "variant"):
325
+ same(row[key], slot[key], "Tau episode schedule binding changed")
326
+ require(row["source_task_sha256"] == slot["task_sha256"] and row["model_calls"] == 0
327
+ and row["disposition"] == "completed", "Tau episode task/disposition changed")
328
+ native_records += len(row["native_calls"])
329
+ require(all(type(call["executed"]) is bool for call in row["native_calls"]),
330
+ "Tau native call execution flag changed")
331
+ native_executed += sum(call["executed"] for call in row["native_calls"])
332
+ match = row["native_db_component"]["db_check"]["db_match"]
333
+ require(type(match) is bool, "Invalid native DB match label")
334
+ counts[(row["domain"], row["variant"], str(match))] += 1
335
+ for capture in row["action_audits"]:
336
+ require(capture["audit_disposition"] == "captured" and capture["inputs_unchanged"] is True
337
+ and capture["withheld_status"] == "unknown", "Tau capture accounting changed")
338
+ verification = capture["trace"]["verifications"][-1]
339
+ require(verification["origin"] == "unknown" and capture["explanation"]["assessment_alignment"] == "unassessable",
340
+ "Tau origin/assessment accounting changed")
341
+ require(verification["status"] in ("satisfied", "violated", "unknown"), "Invalid Tau state label")
342
+ actions[(row["domain"], row["variant"], verification["status"])] += 1
343
+ captures += 1
344
+ same(sorted(summary["files"]), sorted(expected_files), "Tau sealed episode inventory changed")
345
+ inventory(directory, "episodes/**/*", expected_files)
346
+ inventory(directory, "started/**/*", expected_starts)
347
+ same(summary["dispositions"], {"completed": expected_cells, "failed": 0, "unstarted": 0}, "Tau dispositions changed")
348
+ same(summary["native_db_component_counts"], [{"domain": d, "variant": v, "db_match": s == "True", "count": n}
349
+ for (d, v, s), n in sorted(counts.items())], "Tau native DB fractions changed")
350
+ same(summary["action_field_counts"], [{"domain": d, "variant": v, "status": s, "count": n}
351
+ for (d, v, s), n in sorted(actions.items())], "Tau action-field fractions changed")
352
+ require(captures == expected_captures, "Tau capture count changed")
353
+ author_receipt = audit.root / "output/tau3-state-transfer-audit-2026-10-18/final-audit.json"
354
+ if author_receipt.exists():
355
+ saved = audit.json(author_receipt)
356
+ require(saved["protocol_sha256"] == protocol_hash
357
+ and saved["summary_sha256"] == digest((directory / "summary.json").read_bytes()), "Tau author-receipt bindings changed")
358
+ same(saved["start_hash_manifest"], starts, "Tau start file bytes changed")
359
+ return {"completed_cells": expected_cells, "captures": captures, "withheld_unknown": captures,
360
+ "source_revision": protocol["source_revision"], "upstream_commit": protocol["upstream_commit"],
361
+ "native_call_records": native_records, "native_calls_executed": native_executed,
362
+ "upstream_files_checked": len(protocol["upstream_hashes"]),
363
+ "dependency_files_checked": len(protocol["loaded_dependency_hashes"]),
364
+ "model_calls": 0, "protocol_sha256": protocol_hash, "summary_sha256": digest((directory / "summary.json").read_bytes())}
365
+
366
+
367
+ def verify_manifest(audit, manifest):
368
+ manifest = Path(manifest)
369
+ value = audit.json(manifest)
370
+ require(type(value.get("files")) is dict and value["files"], "Missing manifest inventory")
371
+ for name, record in value["files"].items():
372
+ expected = record["sha256"] if isinstance(record, dict) else record
373
+ audit.read(safe_path(manifest.parent, name), expected)
374
+ if "inputs" in value:
375
+ audit.absolute_bindings(value["inputs"])
376
+ fractions = manifest.parent / "fractions.json"
377
+ if fractions.exists():
378
+ for row in audit.json(fractions):
379
+ same(row["value"], None if row["denominator"] in (None, 0) else row["numerator"] / row["denominator"],
380
+ "Figure fraction arithmetic changed")
381
+ return {"manifest_sha256": digest(manifest.read_bytes()), "files_checked": len(value["files"])}
382
+
383
+
384
+ def audit_review_pack(audit, receipt):
385
+ receipt = Path(receipt)
386
+ value = audit.json(receipt)
387
+ archive = safe_path(receipt.parent, value["archive"])
388
+ blob = audit.read(archive, value["archive_sha256"])
389
+ require(len(blob) == value["archive_bytes"], "Review archive size changed")
390
+ with zipfile.ZipFile(archive) as members:
391
+ names = members.namelist()
392
+ require(len(names) == len(set(names)) == value["archive_members"], "Review ZIP inventory changed")
393
+ for name in names:
394
+ relative_name(name)
395
+ require(not any(word in name.casefold() for word in ("private", "mapping", "answer_key")),
396
+ "Private reviewer mapping must not be included in public archive")
397
+ manifest = decode(members.read("manifest.json"))
398
+ same(sorted(names), sorted([*manifest["files"], "manifest.json"]), "Review public inventory changed")
399
+ for name, record in manifest["files"].items():
400
+ data = members.read(name)
401
+ require(len(data) == record["bytes"] and digest(data) == record["sha256"], "Review public member changed")
402
+ return {"archive_sha256": digest(blob), "public_members": len(names),
403
+ "human_validation": "not established by packet integrity; private mappings never read or emitted"}
404
+
405
+
406
+ def main(argv=None):
407
+ parser = argparse.ArgumentParser(description=__doc__)
408
+ parser.add_argument("--root", required=True)
409
+ parser.add_argument("--output", required=True, help="New audit directory; never overwrite")
410
+ parser.add_argument("--relocate", action="append", default=[], metavar="OLD=NEW")
411
+ parser.add_argument("--manifest", action="append", default=[], help="Optional figure/analysis manifest, relative to root")
412
+ parser.add_argument("--review-pack", action="append", default=[], help="Optional public build-receipt, relative to root")
413
+ args = parser.parse_args(argv)
414
+ audit = Audit(args.root, args.relocate)
415
+ output = Path(args.output).resolve()
416
+ require(not output.exists(), "Audit output must be fresh")
417
+ judge = audit_judge(audit, safe_path(audit.root, JUDGE), safe_path(audit.root, ANALYSIS))
418
+ tau = audit_tau(audit, safe_path(audit.root, TAU))
419
+ manifests = [verify_manifest(audit, safe_path(audit.root, name)) for name in args.manifest]
420
+ packs = [audit_review_pack(audit, safe_path(audit.root, name)) for name in args.review_pack]
421
+ result = {"scope": SCOPE, "passed": True, "judge": judge, "tau": tau,
422
+ "audited_utc": datetime.now(timezone.utc).isoformat(), "python": sys.version,
423
+ "auditor_sha256": digest(Path(__file__).read_bytes()),
424
+ "artifact_root": str(audit.root),
425
+ "explicit_relocations": [{"historical_prefix": old, "local_prefix": str(new)}
426
+ for old, new in audit.maps],
427
+ "optional_manifests": manifests, "public_review_packs": packs,
428
+ "bound_files_read": audit.checked, "pinned_git_blobs_checked": audit.git_blobs,
429
+ "model_calls": 0, "native_tool_executions": 0,
430
+ "limits": "Hash/accounting consistency does not authenticate sources, independently validate predicates or supply human judgments."}
431
+ output.mkdir(parents=True, exist_ok=False)
432
+ with (output / "audit.json").open("xb") as stream:
433
+ stream.write(encoded(result))
434
+ (output / "README.md").write_text("# Portable external-validation artifact audit\n\n" + SCOPE
435
+ + ".\n\nThe audit passed with no framework imports, model calls or native tool execution. "
436
+ "See audit.json for closed-study counts, hashes and checking scope. Historical protocols were not rewritten. "
437
+ "No reviewer private mapping was read or emitted.\n\n"
438
+ "The auditor checks the original protocol seals, bound local/upstream/dependency file bytes, "
439
+ "the public judge inputs and exact raw requests/responses, its ordered hash-chained journal, "
440
+ "completed receipts, and recomputed reported fractions/strata. For Tau it checks every planned "
441
+ "episode and start, capture labels, withheld/origin accounting, native call execution counts, "
442
+ "and summary counts. Explicit path relocations are recorded in audit.json; changed working source "
443
+ "is accepted only when its original bytes match the pinned Git object. Missing bindings fail.\n\n"
444
+ "This is artifact integrity and accounting, not source authentication, an independent semantic "
445
+ "oracle, human annotation, or an autonomous-agent benchmark result. Optional figure and review-pack "
446
+ "checks establish their declared hashes and inventory, not the validity of their scientific interpretation.\n",
447
+ encoding="utf-8")
448
+ print(json.dumps({"passed": True, "judge_completed": judge["completed"], "tau_cells": tau["completed_cells"],
449
+ "captures": tau["captures"], "scope": SCOPE}))
450
+ return 0
451
+
452
+
453
+ if __name__ == "__main__":
454
+ try:
455
+ raise SystemExit(main())
456
+ except (ValueError, OSError, KeyError, TypeError, subprocess.CalledProcessError) as error:
457
+ print(f"Audit failed: {error}", file=sys.stderr)
458
+ raise SystemExit(2)