opencomplai-cli 0.9.2__tar.gz → 0.10.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 (186) hide show
  1. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/PKG-INFO +4 -3
  2. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/README.md +2 -1
  3. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/pyproject.toml +2 -2
  4. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/__init__.py +1 -1
  5. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/check_signing.py +11 -2
  6. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/accept.py +64 -12
  7. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/checker.py +4 -0
  8. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/checker_manifest.py +3 -0
  9. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/deployer_pack.py +100 -2
  10. opencomplai_cli-0.10.0/src/opencomplai_cli/commands/export.py +74 -0
  11. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/main.py +15 -0
  12. opencomplai_cli-0.10.0/src/opencomplai_cli/provenance.py +49 -0
  13. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_accept_cmd.py +90 -2
  14. opencomplai_cli-0.10.0/tests/test_check_accepted_real_rows.py +111 -0
  15. opencomplai_cli-0.10.0/tests/test_check_exemption_gate.py +148 -0
  16. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_checker_cmd.py +57 -0
  17. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_deployer_pack_cmd.py +134 -0
  18. opencomplai_cli-0.10.0/tests/test_docs_anchors.py +205 -0
  19. opencomplai_cli-0.10.0/tests/test_export_cmd.py +210 -0
  20. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_oss_sync_gates.py +22 -0
  21. opencomplai_cli-0.10.0/tests/test_provenance_stamp.py +314 -0
  22. opencomplai_cli-0.9.2/tests/test_provenance_stamp.py +0 -110
  23. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/.gitignore +0 -0
  24. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/_encoding.py +0 -0
  25. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/acceptance_gate.py +0 -0
  26. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/agent_inventory_payload.py +0 -0
  27. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/ai_status.py +0 -0
  28. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/__init__.py +0 -0
  29. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/agents.py +0 -0
  30. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/agents_attest.py +0 -0
  31. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/agents_runtime.py +0 -0
  32. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/agents_verify_log.py +0 -0
  33. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/controls.py +0 -0
  34. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/dashboard.py +0 -0
  35. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/diff.py +0 -0
  36. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/halt.py +0 -0
  37. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/incident.py +0 -0
  38. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/instructions.py +0 -0
  39. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/interactive_init.py +0 -0
  40. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/oversight_log.py +0 -0
  41. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/portfolio.py +0 -0
  42. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/push.py +0 -0
  43. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/qms.py +0 -0
  44. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/rules.py +0 -0
  45. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/serve.py +0 -0
  46. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/verify.py +0 -0
  47. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/verify_dossier.py +0 -0
  48. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/connectors/__init__.py +0 -0
  49. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/connectors/_check_args.py +0 -0
  50. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/connectors/github_actions.py +0 -0
  51. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/connectors/gitlab_ci.py +0 -0
  52. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/data/checker-local.html +0 -0
  53. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/exit_codes.py +0 -0
  54. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/inputs.py +0 -0
  55. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/mapped_view.py +0 -0
  56. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/oidc_client.py +0 -0
  57. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/oversight_payload.py +0 -0
  58. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/publish.py +0 -0
  59. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/report_outputs.py +0 -0
  60. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/summaries_emit.py +0 -0
  61. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/__init__.py +0 -0
  62. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/conftest.py +0 -0
  63. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/agents/sample-manifest.json +0 -0
  64. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/agents/sample-scan-report.json +0 -0
  65. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/check_reports_artifact.json +0 -0
  66. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/sarif-2.1.0.subset.schema.json +0 -0
  67. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/trap/trap-manifest.json +0 -0
  68. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_eu_artifact.json +0 -0
  69. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_eu_stdout.json +0 -0
  70. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_nist_artifact.json +0 -0
  71. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_nist_stdout.json +0 -0
  72. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_reports.junit.xml +0 -0
  73. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_reports.sarif.json +0 -0
  74. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_reports.summary.md +0 -0
  75. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_chatbot.json +0 -0
  76. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_chatbot.txt +0 -0
  77. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_credit.json +0 -0
  78. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_credit.txt +0 -0
  79. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_credit_nist.json +0 -0
  80. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_credit_nist.txt +0 -0
  81. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/init_manifest.json +0 -0
  82. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/recommend_files.txt +0 -0
  83. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/report.html +0 -0
  84. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agent_inventory_payload.py +0 -0
  85. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agents_attest.py +0 -0
  86. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agents_cmd.py +0 -0
  87. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agents_import_cmd.py +0 -0
  88. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agents_verify_log.py +0 -0
  89. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ai_docs_claims.py +0 -0
  90. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ai_preload.py +0 -0
  91. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ai_status.py +0 -0
  92. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ai_status_cache.py +0 -0
  93. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_changelog_newest_block.py +0 -0
  94. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_changelog_unreleased.py +0 -0
  95. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_acceptance.py +0 -0
  96. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_acceptance_matrix.py +0 -0
  97. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_gate.py +0 -0
  98. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_output_dir.py +0 -0
  99. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_portfolio.py +0 -0
  100. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_report_exit_codes.py +0 -0
  101. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_reports.py +0 -0
  102. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_roles.py +0 -0
  103. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_sign_gate.py +0 -0
  104. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_signing.py +0 -0
  105. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_summaries.py +0 -0
  106. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_checker_roles.py +0 -0
  107. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ci_integration_guide.py +0 -0
  108. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ci_recipes_guide.py +0 -0
  109. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ci_templates.py +0 -0
  110. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_cli.py +0 -0
  111. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_cli_docs_pages.py +0 -0
  112. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_connector_check_args.py +0 -0
  113. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_connectors.py +0 -0
  114. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_controls_cmd.py +0 -0
  115. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_controls_sync.py +0 -0
  116. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_dashboard_cmd.py +0 -0
  117. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_dead_code_nits.py +0 -0
  118. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_diff_cmd.py +0 -0
  119. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_closeout_guards.py +0 -0
  120. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_generate_push.py +0 -0
  121. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_generate_render.py +0 -0
  122. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_generate_wiring.py +0 -0
  123. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_golive_wording.py +0 -0
  124. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_python_snippets.py +0 -0
  125. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_signing_claims.py +0 -0
  126. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_truth.py +0 -0
  127. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_wording_guards.py +0 -0
  128. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_works_alongside.py +0 -0
  129. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_encoding.py +0 -0
  130. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_eu_ai_scan_golden.py +0 -0
  131. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_eval_provider_base_url.py +0 -0
  132. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_eval_seed_cmd.py +0 -0
  133. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_evidence_provenance.py +0 -0
  134. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_exit_codes.py +0 -0
  135. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_fria_generate.py +0 -0
  136. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gap_report_input.py +0 -0
  137. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_applicability.py +0 -0
  138. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_gpai.py +0 -0
  139. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_map_to.py +0 -0
  140. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_mapped_column.py +0 -0
  141. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_multi_target.py +0 -0
  142. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_nist_rmf_target.py +0 -0
  143. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gate_demo.py +0 -0
  144. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_github_action.py +0 -0
  145. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_golden_eu_output.py +0 -0
  146. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_halt_wire.py +0 -0
  147. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_homebrew_formula.py +0 -0
  148. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_incident_cmd.py +0 -0
  149. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_incident_log_cli.py +0 -0
  150. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_init_model_card.py +0 -0
  151. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_input_robustness.py +0 -0
  152. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_install_scripts.py +0 -0
  153. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_instructions_generate_cmd.py +0 -0
  154. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_iso_42001_target.py +0 -0
  155. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_no_direct_click.py +0 -0
  156. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_oidc_client.py +0 -0
  157. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_oversight_log.py +0 -0
  158. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_oversight_payload.py +0 -0
  159. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_precommit_hooks_pin.py +0 -0
  160. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_public_tree_safety.py +0 -0
  161. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_publish.py +0 -0
  162. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_push_cmd.py +0 -0
  163. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_push_cmd_dossier.py +0 -0
  164. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_qms_generate_cmd.py +0 -0
  165. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_qms_generate_manifest.py +0 -0
  166. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_qms_scaffold_cmd.py +0 -0
  167. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_readme_command_table.py +0 -0
  168. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_readme_quickstart_pin.py +0 -0
  169. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_recommend_gpai.py +0 -0
  170. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_recommend_qms_clauses.py +0 -0
  171. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_regulatory_timeline_cmd.py +0 -0
  172. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_release_pins_parity.py +0 -0
  173. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_rules_changelog_cmd.py +0 -0
  174. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_scan_cmd.py +0 -0
  175. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_scan_quick.py +0 -0
  176. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_security_claims.py +0 -0
  177. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_serve_docs.py +0 -0
  178. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_service_check.py +0 -0
  179. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_sort_priority.py +0 -0
  180. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_summaries_emit.py +0 -0
  181. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_validate_manifest_agents.py +0 -0
  182. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_validate_manifest_oversight.py +0 -0
  183. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_verify_cmd.py +0 -0
  184. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_verify_dossier_kind.py +0 -0
  185. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_verify_kinds.py +0 -0
  186. {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_verify_sbom.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: opencomplai-cli
3
- Version: 0.9.2
3
+ Version: 0.10.0
4
4
  Summary: Opencomplai command-line interface for AI compliance: the EU AI Act, and NIST AI RMF derived from the same evidence
5
5
  Project-URL: Homepage, https://opencomplai.com
6
6
  Project-URL: Documentation, https://docs.opencomplai.com
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Software Development :: Quality Assurance
20
20
  Requires-Python: >=3.11
21
- Requires-Dist: opencomplai-core[reports]>=0.9.2
21
+ Requires-Dist: opencomplai-core[reports]>=0.10.0
22
22
  Requires-Dist: questionary>=2.0.0
23
23
  Requires-Dist: rich>=13.0.0
24
24
  Requires-Dist: typer>=0.9.0
@@ -76,11 +76,12 @@ meta-package instead.
76
76
  | `opencomplai accept` | Write a signed, committable record accepting a system's high-risk classification |
77
77
  | `opencomplai verify-output` | Verify an AI output claim against ground-truth sources |
78
78
  | `opencomplai verify` | Verify a signed artifact (`--kind` artifact, dossier or agent-attestation; `--expect KEY=VALUE` adds offline checks) |
79
+ | `opencomplai export` | Write one neutral evidence package (JSON, or a flat CSV with `--format csv`) from a `compliance-artifact.json`, with the signed artifact embedded unchanged |
79
80
  | `opencomplai version` | Show the installed Opencomplai version |
80
81
  | `opencomplai info` | Show full package metadata (`pip show`-style, across the whole suite) |
81
82
 
82
83
  Also available as command groups (`opencomplai <group> --help` for their own subcommands):
83
- `docs` (Annex IV dossier generation), `instructions` (Art. 13 instructions-for-use), `deployer-pack` (seal instructions-for-use into a hash-named, offline-verifiable pack), `risk` (risk classification), `sync` (metadata sync),
84
+ `docs` (Annex IV dossier generation), `instructions` (Art. 13 instructions-for-use), `deployer-pack` (seal instructions-for-use into a hash-named, offline-verifiable pack; `deployer-pack import` records a provider's pack as upstream evidence), `risk` (risk classification), `sync` (metadata sync),
84
85
  `keys` (signing-key rotation), `ai` (optional AI-intent plugin configuration), `controls`
85
86
  (control-register status), `fria` (fundamental rights impact assessment), `qms` (Art. 17 quality-management-system documents), `dashboard` (Premium Dashboard connection management),
86
87
  `incident` (incident records and deadline clocks),
@@ -43,11 +43,12 @@ meta-package instead.
43
43
  | `opencomplai accept` | Write a signed, committable record accepting a system's high-risk classification |
44
44
  | `opencomplai verify-output` | Verify an AI output claim against ground-truth sources |
45
45
  | `opencomplai verify` | Verify a signed artifact (`--kind` artifact, dossier or agent-attestation; `--expect KEY=VALUE` adds offline checks) |
46
+ | `opencomplai export` | Write one neutral evidence package (JSON, or a flat CSV with `--format csv`) from a `compliance-artifact.json`, with the signed artifact embedded unchanged |
46
47
  | `opencomplai version` | Show the installed Opencomplai version |
47
48
  | `opencomplai info` | Show full package metadata (`pip show`-style, across the whole suite) |
48
49
 
49
50
  Also available as command groups (`opencomplai <group> --help` for their own subcommands):
50
- `docs` (Annex IV dossier generation), `instructions` (Art. 13 instructions-for-use), `deployer-pack` (seal instructions-for-use into a hash-named, offline-verifiable pack), `risk` (risk classification), `sync` (metadata sync),
51
+ `docs` (Annex IV dossier generation), `instructions` (Art. 13 instructions-for-use), `deployer-pack` (seal instructions-for-use into a hash-named, offline-verifiable pack; `deployer-pack import` records a provider's pack as upstream evidence), `risk` (risk classification), `sync` (metadata sync),
51
52
  `keys` (signing-key rotation), `ai` (optional AI-intent plugin configuration), `controls`
52
53
  (control-register status), `fria` (fundamental rights impact assessment), `qms` (Art. 17 quality-management-system documents), `dashboard` (Premium Dashboard connection management),
53
54
  `incident` (incident records and deadline clocks),
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "opencomplai-cli"
3
- version = "0.9.2"
3
+ version = "0.10.0"
4
4
  description = "Opencomplai command-line interface for AI compliance: the EU AI Act, and NIST AI RMF derived from the same evidence"
5
5
  readme = "README.md"
6
6
  license = { text = "AGPL-3.0-only" }
@@ -17,7 +17,7 @@ classifiers = [
17
17
  "Topic :: Software Development :: Quality Assurance",
18
18
  ]
19
19
  dependencies = [
20
- "opencomplai-core[reports]>=0.9.2",
20
+ "opencomplai-core[reports]>=0.10.0",
21
21
  "typer>=0.9.0",
22
22
  "rich>=13.0.0",
23
23
  "questionary>=2.0.0",
@@ -1,6 +1,6 @@
1
1
  """Opencomplai CLI."""
2
2
 
3
- __version__ = "0.9.2"
3
+ __version__ = "0.10.0"
4
4
 
5
5
  # Canonical project metadata — single source of truth for the `--version`
6
6
  # flag and the `opencomplai info` command. Mirrors the [project] table in
@@ -23,20 +23,25 @@ def stamp_artifact(
23
23
  *,
24
24
  now: datetime,
25
25
  manifest_bytes: bytes | None = None,
26
+ repo_root: Path | None = None,
26
27
  ) -> ScanStatusArtifact:
27
28
  """Fill the fields ``publish.prepare_scan_status_artifact`` would add at push,
28
- plus the four provenance stamps, only when unset.
29
+ plus the four provenance stamps and the two tree stamps (``tree_sha256``,
30
+ ``tree_dirty``), only when unset.
29
31
 
30
32
  Stamping before signing makes ``prepared == original`` at push time, so the
31
33
  signature survives. ``manifest_sha256`` stays None without ``manifest_bytes``;
32
34
  CRLF is hashed as LF so one manifest hashes the same on Windows and Linux.
35
+ The tree stamps need ``repo_root`` to be a git work tree and stay None
36
+ otherwise.
33
37
  """
34
38
  from opencomplai_core.json_schemas import SCHEMA_VERSION
35
39
  from opencomplai_core.rules import RULE_SET_VERSION
36
40
 
41
+ from opencomplai_cli.provenance import tree_provenance
37
42
  from opencomplai_cli.publish import _cli_version
38
43
 
39
- update: dict[str, str] = {}
44
+ update: dict[str, object] = {}
40
45
  if not artifact.timestamp:
41
46
  update["timestamp"] = now.isoformat().replace("+00:00", "Z")
42
47
  if not artifact.policy_bundle_version:
@@ -50,4 +55,8 @@ def stamp_artifact(
50
55
  if not artifact.manifest_sha256 and manifest_bytes is not None:
51
56
  lf_bytes = manifest_bytes.replace(b"\r\n", b"\n")
52
57
  update["manifest_sha256"] = hashlib.sha256(lf_bytes).hexdigest()
58
+ if repo_root is not None and not artifact.tree_sha256:
59
+ tree = tree_provenance(repo_root)
60
+ if tree is not None:
61
+ update["tree_sha256"], update["tree_dirty"] = tree
53
62
  return artifact.model_copy(update=update) if update else artifact
@@ -20,6 +20,7 @@ from pathlib import Path
20
20
 
21
21
  import typer
22
22
  from opencomplai_core.acceptance import (
23
+ ART_6_3_EXEMPTION,
23
24
  CLASSIFICATION_ACCEPTANCE,
24
25
  TRAP_APPROVAL,
25
26
  build_record,
@@ -30,7 +31,12 @@ from opencomplai_core.acceptance import (
30
31
  )
31
32
  from opencomplai_core.control_identity import fingerprint_manifest
32
33
  from opencomplai_core.engine import assess
33
- from opencomplai_core.models import AssessmentInput, ModelMetadata, RiskLevel
34
+ from opencomplai_core.models import (
35
+ ART_6_3_GROUND_IDS,
36
+ AssessmentInput,
37
+ ModelMetadata,
38
+ RiskLevel,
39
+ )
34
40
  from opencomplai_core.signing import SigningKeyError, resolve_key
35
41
 
36
42
  from opencomplai_cli.commands.halt import OutputFormat
@@ -88,6 +94,19 @@ def accept_cmd(
88
94
  change_context: str | None = typer.Option(
89
95
  None, "--change-context", help="The change being approved (trap approval only)"
90
96
  ),
97
+ exemption: bool = typer.Option(
98
+ False,
99
+ "--exemption",
100
+ help="Record a declaration that an Annex III system relies on the Art. 6(3) "
101
+ "exceptions (a declaration, not a legal finding; needs legal review)",
102
+ ),
103
+ ground: list[str] | None = typer.Option(
104
+ None,
105
+ "--ground",
106
+ help="Art. 6(3) ground relied on, repeatable (exemption only): hr3_art_6_3, "
107
+ "hr4_narrow_task, hr5_no_significant_risk or hr6_accessory. Default: the "
108
+ "manifest's checker_session.derogation_grounds",
109
+ ),
91
110
  key: Path | None = typer.Option(
92
111
  None,
93
112
  "--key",
@@ -102,10 +121,25 @@ def accept_cmd(
102
121
  prohibited (Art. 5) system; nothing is written on exit 2.
103
122
  """
104
123
  main_mod = _main_module()
105
- record_type = TRAP_APPROVAL if trap_approval else CLASSIFICATION_ACCEPTANCE
124
+ if trap_approval:
125
+ record_type = TRAP_APPROVAL
126
+ elif exemption:
127
+ record_type = ART_6_3_EXEMPTION
128
+ else:
129
+ record_type = CLASSIFICATION_ACCEPTANCE
106
130
 
107
131
  if not accepted_by.strip() or not statement.strip():
108
132
  _fail("--accepted-by and --statement must not be empty.")
133
+ if exemption and trap_approval:
134
+ _fail("--exemption and --trap-approval cannot be combined.")
135
+ if ground and not exemption:
136
+ _fail("--ground only applies with --exemption.")
137
+ unknown = [g for g in ground or [] if g not in ART_6_3_GROUND_IDS]
138
+ if unknown:
139
+ _fail(
140
+ f"unknown --ground {', '.join(unknown)}; "
141
+ f"use one of: {', '.join(ART_6_3_GROUND_IDS)}."
142
+ )
109
143
  if trap_approval and not (change_context and change_context.strip()):
110
144
  _fail("--trap-approval needs --change-context.")
111
145
  if change_context is not None and not trap_approval:
@@ -132,6 +166,17 @@ def accept_cmd(
132
166
  "an acceptance cannot apply to it."
133
167
  )
134
168
 
169
+ grounds: list[str] | None = None
170
+ if exemption:
171
+ cs = manifest.checker_session
172
+ grounds = list(ground) if ground else list(cs.derogation_grounds if cs else [])
173
+ if not grounds:
174
+ _fail(
175
+ "an exemption needs --ground (one or more of: "
176
+ f"{', '.join(ART_6_3_GROUND_IDS)}); the manifest's checker session "
177
+ "has no derogation_grounds to default from."
178
+ )
179
+
135
180
  fingerprint = fingerprint_manifest(manifest)
136
181
  record = sign_record(
137
182
  build_record(
@@ -143,6 +188,7 @@ def accept_cmd(
143
188
  accepted_at=datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
144
189
  public_key_pem=public_pem,
145
190
  change_context=change_context.strip() if trap_approval else None,
191
+ grounds=grounds,
146
192
  ),
147
193
  key_path,
148
194
  )
@@ -155,20 +201,26 @@ def accept_cmd(
155
201
  )
156
202
 
157
203
  if output == OutputFormat.json:
158
- main_mod.console.print_json(
159
- json.dumps(
160
- {
161
- "path": str(path),
162
- "record_type": record_type,
163
- "key_id": key_id_for(public_pem),
164
- "manifest_fingerprint": fingerprint,
165
- }
166
- )
167
- )
204
+ payload = {
205
+ "path": str(path),
206
+ "record_type": record_type,
207
+ "key_id": key_id_for(public_pem),
208
+ "manifest_fingerprint": fingerprint,
209
+ }
210
+ if grounds is not None:
211
+ payload["grounds"] = record["grounds"]
212
+ main_mod.console.print_json(json.dumps(payload))
168
213
  else:
169
214
  main_mod.console.print(
170
215
  f"Wrote {record_type} record: {path}", soft_wrap=True, markup=False
171
216
  )
217
+ if grounds is not None:
218
+ main_mod.console.print(
219
+ "This record is a declaration by a person, not a legal finding; "
220
+ "it needs legal review.",
221
+ soft_wrap=True,
222
+ markup=False,
223
+ )
172
224
  main_mod.console.print(
173
225
  "Commit this file so `opencomplai check` finds it in the repository.",
174
226
  soft_wrap=True,
@@ -22,6 +22,7 @@ from opencomplai_core.compliance_checker import (
22
22
  render_markdown,
23
23
  render_pdf,
24
24
  )
25
+ from opencomplai_core.compliance_checker.bridge import derogation_grounds
25
26
  from opencomplai_core.compliance_checker.catalog import load_help_content
26
27
  from opencomplai_core.compliance_checker.merge import (
27
28
  MergedCheckerResult,
@@ -462,4 +463,7 @@ def build_checker_session_ref(
462
463
  ref["obligation_ids"] = list(obligation_ids)
463
464
  if rationale:
464
465
  ref["rationale"] = list(rationale)
466
+ grounds = derogation_grounds(result)
467
+ if grounds:
468
+ ref["derogation_grounds"] = grounds
465
469
  return ref
@@ -104,6 +104,9 @@ def write_or_append_manifest(
104
104
  old = old if isinstance(old, dict) else {}
105
105
  session = dict(old)
106
106
  session.update(ref)
107
+ if "derogation_grounds" not in ref:
108
+ # Grounds follow the latest run; a stale one must not outlive its answers.
109
+ session.pop("derogation_grounds", None)
107
110
  if _rank(old.get("verdict")) < _rank(ref["verdict"]):
108
111
  session["verdict"] = old["verdict"]
109
112
  if report_path is None and old.get("report_json_path"):
@@ -1,9 +1,10 @@
1
- """`opencomplai deployer-pack build` and the `deployer-pack` verify kind.
1
+ """`opencomplai deployer-pack build` / `import` and the `deployer-pack` verify kind.
2
2
 
3
3
  Seals the Art. 13(3) instructions-for-use JSON (written by `opencomplai
4
4
  instructions generate`) into one portable pack named after its own SHA-256. The
5
5
  pack is generated offline and never uploaded; only its hash and counts reach the
6
- artifact `summaries.packs` (see `opencomplai_core.deployer_pack`).
6
+ artifact `summaries.packs` (see `opencomplai_core.deployer_pack`). `import`
7
+ records a provider's received pack in the manifest `imported_evidence`.
7
8
 
8
9
  `console`/`err_console` and the key paths are obtained lazily from
9
10
  `opencomplai_cli.main` (it imports this module to register the sub-typer, so a
@@ -23,7 +24,10 @@ from pathlib import Path
23
24
  import typer
24
25
  from opencomplai_core import deployer_pack as core
25
26
  from opencomplai_core.instructions_for_use import IFUDocument
27
+ from opencomplai_core.models import SystemManifest
26
28
  from opencomplai_core.signing import SigningKeyError
29
+ from pydantic import ValidationError
30
+ from rich.markup import escape
27
31
 
28
32
  from opencomplai_cli.commands.verify import (
29
33
  VerifyInputError,
@@ -140,6 +144,100 @@ def build_cmd(
140
144
  )
141
145
 
142
146
 
147
+ @app.command("import")
148
+ def import_cmd(
149
+ pack: Path = typer.Argument(..., help="A provider's deployer_pack_<id>.json"),
150
+ manifest: Path = typer.Option(
151
+ Path("system-manifest.json"), "--manifest", "-m", help="Your system manifest"
152
+ ),
153
+ pub_key: Path | None = typer.Option(
154
+ None, "--pub-key", help="Provider's public key; same meaning as `verify`"
155
+ ),
156
+ output: OutputFormat = typer.Option(OutputFormat.human, "--output", "-o"),
157
+ ) -> None:
158
+ """Record a provider's deployer pack in the manifest as upstream evidence.
159
+
160
+ Verifies the pack first and refuses an invalid one. The record is provenance
161
+ only: `check` and `gaps` do not read it, so verdicts do not change.
162
+ """
163
+ from opencomplai_cli import main as _main
164
+
165
+ try:
166
+ result = verify_deployer_pack(pack, pub_key)
167
+ except VerifyInputError as exc:
168
+ _fail(str(exc))
169
+ if result.status == "invalid":
170
+ _fail(
171
+ f"{pack} is not a valid deployer pack: {result.detail}; "
172
+ "nothing was imported"
173
+ )
174
+ parsed = core.DeployerPack.model_validate(json.loads(pack.read_bytes()))
175
+
176
+ try:
177
+ payload = json.loads(manifest.read_text(encoding="utf-8-sig"))
178
+ if not isinstance(payload, dict):
179
+ raise ValueError("not a JSON object")
180
+ SystemManifest.model_validate(payload)
181
+ except (OSError, ValueError) as exc: # ValidationError is a ValueError
182
+ _fail(f"cannot use manifest {manifest}: {exc}")
183
+
184
+ key, entry = core.upstream_evidence(
185
+ parsed, result.status, source_file=pack.name, imported_on=_today()
186
+ )
187
+ stored = payload.get("imported_evidence")
188
+ stored = stored.get(key) if isinstance(stored, dict) else None
189
+ recorded = True
190
+ if isinstance(stored, dict):
191
+ if stored.get("pack_sha256") != entry.pack_sha256:
192
+ _fail(
193
+ f"{manifest} already holds a different pack under {key}; "
194
+ "nothing was imported"
195
+ )
196
+ if (stored.get("verification"), stored.get("signer_key_id")) == (
197
+ entry.verification,
198
+ entry.signer_key_id,
199
+ ):
200
+ recorded = False # keep the first import date, write nothing
201
+ if recorded:
202
+ payload.setdefault("imported_evidence", {})[key] = entry.model_dump(mode="json")
203
+ try:
204
+ SystemManifest.model_validate(payload)
205
+ except ValidationError as exc:
206
+ _fail(f"{manifest} would be invalid, nothing was imported: {exc}")
207
+ manifest.write_text(
208
+ json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8"
209
+ )
210
+
211
+ pack_id = key.split(":", 1)[1]
212
+ if output == OutputFormat.json:
213
+ print(
214
+ json.dumps(
215
+ {
216
+ "pack_id": pack_id,
217
+ "pack_sha256": entry.pack_sha256,
218
+ "provider_system_id": entry.provider_system_id,
219
+ "verification": entry.verification,
220
+ "signer_key_id": entry.signer_key_id,
221
+ "manifest": str(manifest),
222
+ "recorded": recorded,
223
+ }
224
+ )
225
+ )
226
+ return
227
+ verb = "Recorded" if recorded else "Already imported"
228
+ _main.console.print(
229
+ f"[bold]{verb}[/bold] deployer pack {pack_id} from provider system "
230
+ f"{escape(entry.provider_system_id)} ({entry.verification}) in "
231
+ f"{escape(str(manifest))}",
232
+ soft_wrap=True,
233
+ )
234
+ if entry.verification == "unsigned":
235
+ _main.console.print(
236
+ " unsigned: only the hash was checked, no signature was checked",
237
+ soft_wrap=True,
238
+ )
239
+
240
+
143
241
  def verify_deployer_pack(path: Path, pub_key: Path | None) -> VerifyResult:
144
242
  import jsonschema
145
243
 
@@ -0,0 +1,74 @@
1
+ """`opencomplai export` -- write one neutral evidence package from an artifact.
2
+
3
+ Moves data, never gates: exit 0 whatever the verdict inside. Reads only the
4
+ artifact path it is given; writes only `--out` and `--artifact-out`. Nothing is
5
+ sent anywhere. `err_console` comes lazily from `opencomplai_cli.main` (a
6
+ module-level import back into main would be circular).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import sys
13
+ from enum import StrEnum
14
+ from pathlib import Path
15
+
16
+ import typer
17
+ from opencomplai_core.evidence_package import build_evidence_package, package_to_csv
18
+ from rich.markup import escape
19
+
20
+ from opencomplai_cli.inputs import read_json_file
21
+
22
+
23
+ class ExportFormat(StrEnum):
24
+ json = "json"
25
+ csv = "csv"
26
+
27
+
28
+ def export_cmd(
29
+ artifact: Path = typer.Argument(
30
+ ..., help="Path to a compliance-artifact.json written by `check --with-gaps`"
31
+ ),
32
+ fmt: ExportFormat = typer.Option(
33
+ ExportFormat.json,
34
+ "--format",
35
+ help="json (the package) or csv (one row per article)",
36
+ ),
37
+ out: Path | None = typer.Option(
38
+ None, "--out", help="Write the package to this file instead of stdout"
39
+ ),
40
+ artifact_out: Path | None = typer.Option(
41
+ None,
42
+ "--artifact-out",
43
+ help="Also write the embedded artifact, ready for `opencomplai verify`",
44
+ ),
45
+ ) -> None:
46
+ """Export one neutral, schema-validated evidence package (JSON or CSV)."""
47
+ from opencomplai_cli import main as _main
48
+
49
+ try:
50
+ raw = read_json_file(artifact)
51
+ pkg = build_evidence_package(raw)
52
+ except (OSError, ValueError) as e:
53
+ _main.err_console.print(f"[red]Error:[/red] {escape(str(e))}")
54
+ sys.exit(2)
55
+
56
+ if fmt == ExportFormat.csv:
57
+ text = package_to_csv(pkg)
58
+ else:
59
+ text = pkg.model_dump_json(indent=2) + "\n"
60
+ try:
61
+ if artifact_out is not None:
62
+ # Same bytes semantics the signature covers: parses to the same model.
63
+ artifact_out.write_text(
64
+ json.dumps(pkg.artifact, indent=2) + "\n",
65
+ encoding="utf-8",
66
+ newline="",
67
+ )
68
+ if out is not None:
69
+ out.write_text(text, encoding="utf-8", newline="")
70
+ except OSError as e:
71
+ _main.err_console.print(f"[red]Error:[/red] {escape(str(e))}")
72
+ sys.exit(2)
73
+ if out is None:
74
+ typer.echo(text, nl=False)
@@ -50,6 +50,7 @@ from opencomplai_core.deployer_pack import with_pack_summary
50
50
  from opencomplai_core.engine import assess
51
51
  from opencomplai_core.eval_engine import eval_summary_from_report, run_evals
52
52
  from opencomplai_core.evaluators.seed_corpus import seed_sample_set
53
+ from opencomplai_core.exemption_gap import apply_exemption_gate
53
54
  from opencomplai_core.frameworks import (
54
55
  EU_AI_ACT,
55
56
  evaluate_targets,
@@ -184,6 +185,7 @@ from opencomplai_cli.commands.deployer_pack import ( # noqa: E402
184
185
  app as deployer_pack_app,
185
186
  )
186
187
  from opencomplai_cli.commands.diff import diff_cmd # noqa: E402
188
+ from opencomplai_cli.commands.export import export_cmd # noqa: E402
187
189
  from opencomplai_cli.commands.halt import approve_cmd, resume_cmd # noqa: E402
188
190
  from opencomplai_cli.commands.incident import app as incident_app # noqa: E402
189
191
  from opencomplai_cli.commands.instructions import app as instructions_app # noqa: E402
@@ -231,6 +233,7 @@ app.command("resume")(resume_cmd)
231
233
  app.command("accept")(accept_cmd)
232
234
  app.command("diff")(diff_cmd)
233
235
  app.command("verify")(verify_cmd)
236
+ app.command("export")(export_cmd)
234
237
 
235
238
  console = Console()
236
239
  err_console = Console(stderr=True)
@@ -3259,6 +3262,14 @@ def check_cmd(
3259
3262
  "Replaces opencomplai.yaml's gate.fail_on."
3260
3263
  ),
3261
3264
  ),
3265
+ require_exemption_record: bool = typer.Option(
3266
+ False,
3267
+ "--require-exemption-record",
3268
+ help=(
3269
+ "Opt-in: fail (exit 1) when the checker session relies on the "
3270
+ "Art. 6(3) derogation and no valid signed exemption record exists."
3271
+ ),
3272
+ ),
3262
3273
  output_dir: Path = typer.Option(
3263
3274
  Path("."),
3264
3275
  "--output-dir",
@@ -3459,6 +3470,9 @@ def check_cmd(
3459
3470
  corroboration_report=scan_report,
3460
3471
  sample_set=sample_set,
3461
3472
  )
3473
+ artifact = apply_exemption_gate(
3474
+ artifact, manifest, repo_root=repo_root, require=require_exemption_record
3475
+ )
3462
3476
 
3463
3477
  # HALT-WIRE / E-12(a): halt trigger — trap always halts; an unresolved
3464
3478
  # HIGH-risk corroboration gap (--scan --fail-on ... failed) halts too,
@@ -3562,6 +3576,7 @@ def check_cmd(
3562
3576
  artifact,
3563
3577
  now=datetime.now(UTC),
3564
3578
  manifest_bytes=manifest_file.read_bytes() if manifest_file.is_file() else None,
3579
+ repo_root=repo_root,
3565
3580
  )
3566
3581
  # Before signing, so the signature covers the packs and other summaries.
3567
3582
  artifact = with_pack_summary(artifact, repo_root / "deployer-pack")
@@ -0,0 +1,49 @@
1
+ """Tree provenance for ``check``: content digest and dirty flag from git."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ from opencomplai_core.tree_digest import tree_digest
10
+
11
+
12
+ def _git(repo_root: Path, *args: str) -> bytes | None:
13
+ try:
14
+ proc = subprocess.run(
15
+ ["git", *args],
16
+ cwd=repo_root,
17
+ capture_output=True,
18
+ timeout=30,
19
+ check=False,
20
+ )
21
+ except (OSError, subprocess.SubprocessError):
22
+ return None
23
+ return proc.stdout if proc.returncode == 0 else None
24
+
25
+
26
+ def tree_provenance(repo_root: Path) -> tuple[str, bool] | None:
27
+ """``(tree_sha256, tree_dirty)`` for the git tree at ``repo_root``, else None.
28
+
29
+ The digest covers tracked plus untracked non-ignored file content. Dirty is
30
+ true for any tracked modification or any untracked non-ignored file. The
31
+ ``.`` pathspec keeps paths relative to ``repo_root``, so one tree in two
32
+ directories gives one digest.
33
+
34
+ ponytail: whole-file reads, submodules are not descended, and a
35
+ ``.gitattributes`` text conversion can report a clean checkout as dirty on
36
+ one platform; add streaming or conversion handling if that bites.
37
+ """
38
+ tracked = _git(repo_root, "ls-files", "-z", "--", ".")
39
+ untracked = _git(
40
+ repo_root, "ls-files", "-z", "--others", "--exclude-standard", "--", "."
41
+ )
42
+ status = _git(repo_root, "status", "--porcelain", "--", ".")
43
+ if not tracked or untracked is None or status is None:
44
+ return None
45
+ paths = {
46
+ os.fsdecode(p) for blob in (tracked, untracked) for p in blob.split(b"\0") if p
47
+ }
48
+ digest = tree_digest((p, repo_root / p) for p in paths)
49
+ return digest, bool(status.strip())