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.
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/PKG-INFO +4 -3
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/README.md +2 -1
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/pyproject.toml +2 -2
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/__init__.py +1 -1
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/check_signing.py +11 -2
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/accept.py +64 -12
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/checker.py +4 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/checker_manifest.py +3 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/deployer_pack.py +100 -2
- opencomplai_cli-0.10.0/src/opencomplai_cli/commands/export.py +74 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/main.py +15 -0
- opencomplai_cli-0.10.0/src/opencomplai_cli/provenance.py +49 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_accept_cmd.py +90 -2
- opencomplai_cli-0.10.0/tests/test_check_accepted_real_rows.py +111 -0
- opencomplai_cli-0.10.0/tests/test_check_exemption_gate.py +148 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_checker_cmd.py +57 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_deployer_pack_cmd.py +134 -0
- opencomplai_cli-0.10.0/tests/test_docs_anchors.py +205 -0
- opencomplai_cli-0.10.0/tests/test_export_cmd.py +210 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_oss_sync_gates.py +22 -0
- opencomplai_cli-0.10.0/tests/test_provenance_stamp.py +314 -0
- opencomplai_cli-0.9.2/tests/test_provenance_stamp.py +0 -110
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/.gitignore +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/_encoding.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/acceptance_gate.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/agent_inventory_payload.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/ai_status.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/__init__.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/agents.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/agents_attest.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/agents_runtime.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/agents_verify_log.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/controls.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/dashboard.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/diff.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/halt.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/incident.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/instructions.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/interactive_init.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/oversight_log.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/portfolio.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/push.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/qms.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/rules.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/serve.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/verify.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/verify_dossier.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/connectors/__init__.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/connectors/_check_args.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/connectors/github_actions.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/connectors/gitlab_ci.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/data/checker-local.html +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/exit_codes.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/inputs.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/mapped_view.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/oidc_client.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/oversight_payload.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/publish.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/report_outputs.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/summaries_emit.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/__init__.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/conftest.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/agents/sample-manifest.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/agents/sample-scan-report.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/check_reports_artifact.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/sarif-2.1.0.subset.schema.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/fixtures/trap/trap-manifest.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_eu_artifact.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_eu_stdout.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_nist_artifact.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_nist_stdout.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_reports.junit.xml +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_reports.sarif.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/check_reports.summary.md +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_chatbot.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_chatbot.txt +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_credit.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_credit.txt +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_credit_nist.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/gaps_credit_nist.txt +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/init_manifest.json +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/recommend_files.txt +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/golden/report.html +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agent_inventory_payload.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agents_attest.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agents_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agents_import_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_agents_verify_log.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ai_docs_claims.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ai_preload.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ai_status.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ai_status_cache.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_changelog_newest_block.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_changelog_unreleased.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_acceptance.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_acceptance_matrix.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_gate.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_output_dir.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_portfolio.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_report_exit_codes.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_reports.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_roles.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_sign_gate.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_signing.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_check_summaries.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_checker_roles.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ci_integration_guide.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ci_recipes_guide.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_ci_templates.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_cli.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_cli_docs_pages.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_connector_check_args.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_connectors.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_controls_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_controls_sync.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_dashboard_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_dead_code_nits.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_diff_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_closeout_guards.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_generate_push.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_generate_render.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_generate_wiring.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_golive_wording.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_python_snippets.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_signing_claims.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_truth.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_wording_guards.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_docs_works_alongside.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_encoding.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_eu_ai_scan_golden.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_eval_provider_base_url.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_eval_seed_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_evidence_provenance.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_exit_codes.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_fria_generate.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gap_report_input.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_applicability.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_gpai.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_map_to.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_mapped_column.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_multi_target.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gaps_nist_rmf_target.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_gate_demo.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_github_action.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_golden_eu_output.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_halt_wire.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_homebrew_formula.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_incident_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_incident_log_cli.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_init_model_card.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_input_robustness.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_install_scripts.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_instructions_generate_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_iso_42001_target.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_no_direct_click.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_oidc_client.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_oversight_log.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_oversight_payload.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_precommit_hooks_pin.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_public_tree_safety.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_publish.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_push_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_push_cmd_dossier.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_qms_generate_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_qms_generate_manifest.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_qms_scaffold_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_readme_command_table.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_readme_quickstart_pin.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_recommend_gpai.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_recommend_qms_clauses.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_regulatory_timeline_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_release_pins_parity.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_rules_changelog_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_scan_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_scan_quick.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_security_claims.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_serve_docs.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_service_check.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_sort_priority.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_summaries_emit.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_validate_manifest_agents.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_validate_manifest_oversight.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_verify_cmd.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_verify_dossier_kind.py +0 -0
- {opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/tests/test_verify_kinds.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
{opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/checker_manifest.py
RENAMED
|
@@ -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"):
|
{opencomplai_cli-0.9.2 → opencomplai_cli-0.10.0}/src/opencomplai_cli/commands/deployer_pack.py
RENAMED
|
@@ -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())
|