xrefkit 0.7.6__tar.gz → 0.7.7__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.
- {xrefkit-0.7.6/xrefkit.egg-info → xrefkit-0.7.7}/PKG-INFO +1 -1
- {xrefkit-0.7.6 → xrefkit-0.7.7}/pyproject.toml +1 -1
- xrefkit-0.7.7/tests/test_governance_host.py +248 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/__init__.py +1 -1
- xrefkit-0.7.7/xrefkit/governance_host.py +378 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7/xrefkit.egg-info}/PKG-INFO +1 -1
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit.egg-info/SOURCES.txt +2 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/LICENSE +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/README.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/setup.cfg +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_artifact_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_attention_pet.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_attention_pet_client_protocol.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_attention_pet_codex_session.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_attention_pet_fit.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_azure_connection.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_azure_import.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_azure_recovery.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_azure_update_candidate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_azure_writer.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_boundary_analysis.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_brownfield_csharp_structure_definitions.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_brownfield_file_editing_protocol.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_business_intake_conversation_definitions.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_business_intake_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_calibration_lint.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_catalog_preparation_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_check_skill_knowledge_xids.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_ci_workflow_invariants.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_code_constraint_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_collect_analyzer_sarif.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_compact_skill_logs.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_constraint_derivation_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_constraint_derivation_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_contribution_adoption_bytes.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_contribution_returns.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_convert_to_xrefkit_skill.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_cs_scope_probe.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_csharp_commonality.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_csharp_naming_profile.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_ctx.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_cutover_readiness.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_dashboard.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_database_batch_definition_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_decision_trace.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_design_business_intake_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_design_planning_definition_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_dotnet_definition_candidate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_editorial_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_editorial_intake_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_editorial_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_embedded_startup_pack.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_error_policy_audit.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_error_policy_locator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_execution_binding.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_existing_skill_definition_batch.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_final_skilldefinition_batch.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_flow_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_fm_multiroot.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_gate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_gateway.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_gateway_mcp.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_gateway_skill_adapter.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_gateway_work_items.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_goal_desired_state.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_governance_entry.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_host_precheck.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_human_evaluation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_implementation_flow_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_implementation_review_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_inbound_uploads.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_instruction_workflow.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_knowledge_relations_validator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_language_review_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_legacy_guidance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_legacy_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_legacy_resources.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_mcp_package_routing.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_mcp_setup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_mcp_subagent_startup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_mcp_subagent_startup_integration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_ontology_assessment_validator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_os_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_os_knowledge_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_os_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_ownership.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_package_skill_run.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_packaged_native_governance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_packmeta.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_plan_markdown.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_plan_observation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_project_management_materialization.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_project_quality_baseline.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_qa_report_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_reporting_profiles.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_reporting_quality.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_repository_skill_adoption.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_repository_startup_pack.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_resource_provider.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_runtime_contracts.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_sarif_to_locator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_security_definition_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_site_release_metadata.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skill_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skill_definition_governance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skill_definition_mcp.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skill_definition_runtime.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skill_edits.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skill_maturity_return.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skill_release_compatibility.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skill_runtime_audit.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skillmeta.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_skills_sync.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_source_analysis_fragments.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_structure_catalog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_subagent_startup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_subagent_startup_boundaries.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_wbs.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_work_management.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_work_packages.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_workflow_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_xref.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_xrefkit_instance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_xrefkit_tools.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_xrefkit_v2_discovery.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_xrefkit_v2_models.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/tests/test_xrefkit_v2_pipeline.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/__main__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/__init__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/__main__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/client.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/client_protocol.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/codex_session.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/evaluator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/model.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/presentation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/profiles.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/scenarios.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/server.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/attention_pet/store.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/azure_connection.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/azure_import.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/azure_recovery.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/azure_update_candidate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/azure_writer.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/boundary_analysis.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/catalog_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/contracts.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/ctx.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/dashboard.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/decision_trace.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/discovery.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/execution_binding.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/execution_binding_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/gate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/gateway.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/goalstate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/governance_entry.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/governance_entry_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/hashing.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/host_precheck.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/import_skill.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/instance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/loaders.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/__init__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/audit.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/bootstrap.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/catalog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/client_cache.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/client_flow.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/context_registry.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/context_token.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/contracts.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/contribution_adoption.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/contribution_returns.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/dist.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/gateway.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/governed_overlays.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/inbound_uploads.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/knowledge_edits.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/legacy_guidance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/legacy_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/legacy_resources.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/ownership.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/repository.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/schemas.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/server.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/setup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/skill_edits.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/skill_maturity.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/startup_contract_pack.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/subagent_startup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp/update_gate_intake.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/mcp_tools.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/__init__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/common.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/effective_bundle.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/human_evaluation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/local_manifest.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/package_manifest.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/run_log.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/server_config.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/models/skill_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/operations_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/ownership.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/packmeta.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/plan_markdown.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/plan_observation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/registry.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/reporting.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/reporting_profiles.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/repository_skills.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resolver.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resource_provider.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/attention_pet/client-state.schema.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/attention_pet/pet.css +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/attention_pet/pet.html +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/attention_pet/pet.js +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/contracts.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/current.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/model_body.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/startup_sources/000_agent_entry.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/startup_sources/011_startup_xref_routing.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/startup_sources/015_shared_memory_operations.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/startup_sources/016_uncertainty_protocol.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/startup_sources/017_base_and_xref_layering.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/startup_sources/053_context_direction_security_guard.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/base/startup_sources/yomiyasu_LICENSE.txt +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/migration/docs/guides/062_legacy_flow_skill_migration_guide.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/migration/knowledge/operations/130_legacy_flow_skill_migration_rules.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/migration/knowledge/organization/160_context_direction_guard_rules.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/migration/skills/os/legacy_flow_skill_migration/SKILL.v1.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/migration/skills/os/legacy_flow_skill_migration/references/legacy_flow_skill_migration_template.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/migration/skills/repository_adoption.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/resources/migration/tools/migrate_legacy_flow_skill.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/runlog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/skill_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/skill_definition_catalog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/skill_definition_governance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/skillmeta.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/skillrun.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/skills_sync.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/structure_catalog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/subagent_startup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/tools/__init__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/tools/__main__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/v2_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/wbs.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/work_management.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/work_management_view.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/workspace.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit/xref.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit.egg-info/dependency_links.txt +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit.egg-info/entry_points.txt +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit.egg-info/requires.txt +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.7}/xrefkit.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Local host integration checks use simulated subprocess/UI, never real approval."""
|
|
2
|
+
import copy
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import subprocess
|
|
6
|
+
from types import SimpleNamespace
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from test_governance_entry import case, host_receipt, HOST_KEY
|
|
11
|
+
from test_subagent_startup import startup
|
|
12
|
+
from xrefkit.governance_entry import digest, validate_result
|
|
13
|
+
from xrefkit.mcp.contribution_adoption import HmacHumanApprovalVerifier
|
|
14
|
+
from xrefkit import governance_host as host
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def local(case, monkeypatch):
|
|
19
|
+
root, packet, _ = case
|
|
20
|
+
packet_file = root / "work/packet.json"
|
|
21
|
+
packet_file.write_text(json.dumps(packet))
|
|
22
|
+
receipt = host_receipt(root, packet)
|
|
23
|
+
receipt_file = root / "work/receipt.json"
|
|
24
|
+
receipt_file.write_text(json.dumps(receipt))
|
|
25
|
+
state_file = root / "authority.dpapi"
|
|
26
|
+
state = {"schema": "xrefkit.local_host_authority/v1", "root": str(root),
|
|
27
|
+
"packet_hash": packet["packet_hash"], "receipt_hash": digest(receipt),
|
|
28
|
+
"host_secret": HOST_KEY, "applied": False}
|
|
29
|
+
monkeypatch.setattr(host, "_load_state", lambda path: copy.deepcopy(state))
|
|
30
|
+
monkeypatch.setattr(host, "_save_state", lambda path, value, **kwargs: state.update(value))
|
|
31
|
+
return root, packet, packet_file, receipt_file, state_file, state
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_native_decline_never_applies_or_issues_human_secret(local, monkeypatch):
|
|
35
|
+
root, _, packet, receipt, state_file, state = local
|
|
36
|
+
monkeypatch.setattr(host, "_review_dialog", lambda *args: False)
|
|
37
|
+
result = host.apply_local(root, packet, receipt, state_file, root / "work/reflection.json")
|
|
38
|
+
assert result["status"] == "human_declined_no_application"
|
|
39
|
+
assert not (root / "knowledge/item.md").exists()
|
|
40
|
+
assert "human_secret" not in state and not state["applied"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_native_action_applies_with_separate_keys_and_stale_replay_refuses(local, monkeypatch):
|
|
44
|
+
root, _, packet, receipt, state_file, state = local
|
|
45
|
+
observed = []
|
|
46
|
+
monkeypatch.setattr(host, "_review_dialog", lambda *args: observed.append(args) or True)
|
|
47
|
+
result = host.apply_local(root, packet, receipt, state_file, root / "work/reflection.json")
|
|
48
|
+
assert result["runtime_activation"] == "not_performed"
|
|
49
|
+
assert observed[0][0] == "knowledge/item.md" and "+# New" in observed[0][2]
|
|
50
|
+
assert state["human_secret"] != state["host_secret"]
|
|
51
|
+
assert (root / "knowledge/item.md").read_bytes() == (root / "work/candidate.md").read_bytes()
|
|
52
|
+
with pytest.raises(ValueError, match="unapplied dispatch"):
|
|
53
|
+
host.apply_local(root, packet, receipt, state_file, root / "work/replay.json")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.mark.parametrize("mutation", ["candidate", "target"])
|
|
57
|
+
def test_change_during_dialog_refuses_even_after_ui_accepts(local, monkeypatch, mutation):
|
|
58
|
+
root, _, packet, receipt, state_file, state = local
|
|
59
|
+
def change(*args):
|
|
60
|
+
path = root / ("work/candidate.md" if mutation == "candidate" else "knowledge/item.md")
|
|
61
|
+
path.parent.mkdir(exist_ok=True)
|
|
62
|
+
path.write_text("external change")
|
|
63
|
+
return True
|
|
64
|
+
monkeypatch.setattr(host, "_review_dialog", change)
|
|
65
|
+
with pytest.raises(ValueError, match="changed"):
|
|
66
|
+
host.apply_local(root, packet, receipt, state_file, root / "work/reflection.json")
|
|
67
|
+
assert not state["applied"]
|
|
68
|
+
if mutation == "target":
|
|
69
|
+
assert (root / "knowledge/item.md").read_text() == "external change"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.mark.parametrize("field", ["packet_hash", "receipt_hash", "root"])
|
|
73
|
+
def test_wrong_dispatch_state_rejected_before_dialog(local, monkeypatch, field):
|
|
74
|
+
root, _, packet, receipt, state_file, state = local
|
|
75
|
+
state[field] = "wrong"
|
|
76
|
+
monkeypatch.setattr(host, "_review_dialog", lambda *args: pytest.fail("UI must not open"))
|
|
77
|
+
with pytest.raises(ValueError, match="does not match"):
|
|
78
|
+
host.apply_local(root, packet, receipt, state_file, root / "work/reflection.json")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_failed_gate_cannot_be_human_approved(local, monkeypatch):
|
|
82
|
+
root, packet, packet_file, receipt_file, state_file, state = local
|
|
83
|
+
receipt = host_receipt(root, packet, failed=True)
|
|
84
|
+
receipt_file.write_text(json.dumps(receipt))
|
|
85
|
+
state["receipt_hash"] = digest(receipt)
|
|
86
|
+
monkeypatch.setattr(host, "_review_dialog", lambda *args: pytest.fail("UI must not open"))
|
|
87
|
+
with pytest.raises(ValueError, match="failed or unknown"):
|
|
88
|
+
host.apply_local(root, packet_file, receipt_file, state_file, root / "work/reflection.json")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_tampered_signature_refused_even_with_matching_receipt_digest(local, monkeypatch):
|
|
92
|
+
root, _, packet, receipt_file, state_file, state = local
|
|
93
|
+
receipt = json.loads(receipt_file.read_text())
|
|
94
|
+
receipt["signature"] = "wrong"
|
|
95
|
+
receipt_file.write_text(json.dumps(receipt))
|
|
96
|
+
state["receipt_hash"] = digest(receipt)
|
|
97
|
+
monkeypatch.setattr(host, "_review_dialog", lambda *args: pytest.fail("UI must not open"))
|
|
98
|
+
with pytest.raises(ValueError, match="signature"):
|
|
99
|
+
host.apply_local(root, packet, receipt_file, state_file, root / "work/reflection.json")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_evidence_destination_cannot_overwrite_packet(local, monkeypatch):
|
|
103
|
+
root, _, packet, receipt, state_file, _ = local
|
|
104
|
+
monkeypatch.setattr(host, "_review_dialog", lambda *args: pytest.fail("UI must not open"))
|
|
105
|
+
with pytest.raises(ValueError, match="overwrite"):
|
|
106
|
+
host.apply_local(root, packet, receipt, state_file, packet)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.parametrize("events", [[], [{"type": "thread.started", "thread_id": "x"}],
|
|
110
|
+
[{"type": "thread.started", "thread_id": "x"}, {"type": "turn.failed"}],
|
|
111
|
+
[{"type": "thread.started", "thread_id": "x"}, {"type": "turn.completed"}, {"type": "turn.completed"}]])
|
|
112
|
+
def test_missing_or_failed_terminal_events_are_not_execution(tmp_path, events):
|
|
113
|
+
path = tmp_path / "events.jsonl"
|
|
114
|
+
path.write_text("\n".join(json.dumps(event) for event in events))
|
|
115
|
+
with pytest.raises(ValueError):
|
|
116
|
+
host._completed_session(path)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_valid_single_completion(tmp_path):
|
|
120
|
+
path = tmp_path / "events.jsonl"
|
|
121
|
+
path.write_text('{"type":"thread.started","thread_id":"actual-session"}\n{"type":"turn.completed"}\n')
|
|
122
|
+
assert host._completed_session(path) == "actual-session"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_authority_never_inherited_by_child(monkeypatch):
|
|
126
|
+
monkeypatch.setenv("XREFKIT_GOVERNANCE_HOST_SECRET", "private")
|
|
127
|
+
monkeypatch.setenv("XREFKIT_GOVERNANCE_HUMAN_SECRET", "private")
|
|
128
|
+
monkeypatch.setenv("XREFKIT_CONTRIBUTION_APPROVAL_SECRET", "private")
|
|
129
|
+
env = host._child_environment()
|
|
130
|
+
assert not any(key.startswith("XREFKIT_GOVERNANCE_") for key in env)
|
|
131
|
+
assert "XREFKIT_CONTRIBUTION_APPROVAL_SECRET" not in env
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_child_open_refusal_never_dispatches(case, monkeypatch):
|
|
135
|
+
root, packet, _ = case
|
|
136
|
+
source = root / "work/packet.json"
|
|
137
|
+
source.write_text(json.dumps(packet))
|
|
138
|
+
binary = root / "codex.exe"
|
|
139
|
+
binary.write_bytes(b"test fixture, never executable")
|
|
140
|
+
monkeypatch.setattr(host.subprocess, "run", lambda *args, **kwargs:
|
|
141
|
+
SimpleNamespace(returncode=2, stdout='{"ok":false}'))
|
|
142
|
+
monkeypatch.setattr(host.subprocess, "Popen", lambda *args, **kwargs: pytest.fail("must not dispatch"))
|
|
143
|
+
with pytest.raises(ValueError, match="Skill run refused"):
|
|
144
|
+
host.dispatch_local(root, source, binary, root / "work/dispatch")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_process_failure_never_generates_host_authority(case, monkeypatch):
|
|
148
|
+
root, packet, _ = case
|
|
149
|
+
source = root / "work/packet.json"
|
|
150
|
+
source.write_text(json.dumps(packet))
|
|
151
|
+
binary = root / "codex.exe"
|
|
152
|
+
binary.write_bytes(b"test fixture, never executable")
|
|
153
|
+
monkeypatch.setattr(host.subprocess, "run", lambda *args, **kwargs:
|
|
154
|
+
SimpleNamespace(returncode=0, stdout='{"ok":true,"run_id":"opened"}'))
|
|
155
|
+
process = SimpleNamespace(returncode=7, pid=123, communicate=lambda *args, **kwargs: None)
|
|
156
|
+
monkeypatch.setattr(host.subprocess, "Popen", lambda *args, **kwargs: process)
|
|
157
|
+
monkeypatch.setattr(host.secrets, "token_hex", lambda *args: pytest.fail("key must not be generated"))
|
|
158
|
+
with pytest.raises(ValueError, match="process failed"):
|
|
159
|
+
host.dispatch_local(root, source, binary, root / "work/dispatch")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_no_cli_automatic_approval_option():
|
|
163
|
+
with pytest.raises(SystemExit) as exc:
|
|
164
|
+
host.main(["--root", ".", "apply", "--packet", "a", "--receipt", "b",
|
|
165
|
+
"--authority-state", "c", "--out", "d", "--yes"])
|
|
166
|
+
assert exc.value.code == 2
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_applied_but_authority_save_failure_carries_reflection(local, monkeypatch):
|
|
170
|
+
root, _, packet, receipt, state_file, _ = local
|
|
171
|
+
monkeypatch.setattr(host, "_review_dialog", lambda *args: True)
|
|
172
|
+
def fail_save(*args, **kwargs):
|
|
173
|
+
raise OSError("simulated authority disk failure")
|
|
174
|
+
monkeypatch.setattr(host, "_save_state", fail_save)
|
|
175
|
+
with pytest.raises(host.AppliedEvidenceError) as exc:
|
|
176
|
+
host.apply_local(root, packet, receipt, state_file, root / "work/reflection.json")
|
|
177
|
+
assert (root / "knowledge/item.md").read_bytes() == (root / "work/candidate.md").read_bytes()
|
|
178
|
+
assert exc.value.reflection["target"]["path"] == "knowledge/item.md"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_cli_distinguishes_applied_evidence_failure(monkeypatch, capsys):
|
|
182
|
+
reflection = {"target": {"path": "skills/example/SKILL.v1.md", "sha256": "observed-hash"}}
|
|
183
|
+
def failed(*args):
|
|
184
|
+
raise host.AppliedEvidenceError(reflection)
|
|
185
|
+
monkeypatch.setattr(host, "apply_local", failed)
|
|
186
|
+
code = host.main(["--root", ".", "apply", "--packet", "a", "--receipt", "b",
|
|
187
|
+
"--authority-state", "c", "--out", "d"])
|
|
188
|
+
assert code == 3
|
|
189
|
+
assert "observed-hash" in capsys.readouterr().out
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@pytest.mark.parametrize("invalid_run", [None, "identity", "escalated"])
|
|
193
|
+
def test_dispatch_attests_only_actual_opened_run_and_completed_output(case, monkeypatch, invalid_run):
|
|
194
|
+
root, packet, _ = case
|
|
195
|
+
source = root / "work/packet.json"
|
|
196
|
+
source.write_text(json.dumps(packet))
|
|
197
|
+
binary = root / "codex.exe"
|
|
198
|
+
binary.write_bytes(b"test fixture, never executable")
|
|
199
|
+
host_receipt(root, packet) # Test-only prepared body/closed log.
|
|
200
|
+
out = root / "work/dispatch"
|
|
201
|
+
state = {}
|
|
202
|
+
child_path = None
|
|
203
|
+
def open_run(argv, **kwargs):
|
|
204
|
+
nonlocal child_path
|
|
205
|
+
child_path = Path(argv[argv.index("--out") + 1])
|
|
206
|
+
assert child_path.name.endswith("_skill_run_shared_asset_update_gate.md")
|
|
207
|
+
text = (root / "work/child.md").read_text().replace("work/analysis.md", "work/dispatch/analysis.json")
|
|
208
|
+
child_path.write_text(text)
|
|
209
|
+
return SimpleNamespace(returncode=0, stdout='{"ok":true,"run_id":"child-test-run"}')
|
|
210
|
+
def launch(argv, **kwargs):
|
|
211
|
+
def communicate(prompt, **options):
|
|
212
|
+
assert b"Do not open another run" in prompt
|
|
213
|
+
(out / "analysis.json").write_bytes((root / "work/analysis.md").read_bytes())
|
|
214
|
+
Path(argv[argv.index("--output-last-message") + 1]).write_text("Analysis complete")
|
|
215
|
+
kwargs["stdout"].write(b'{"type":"thread.started","thread_id":"simulated-session"}\n{"type":"turn.completed"}\n')
|
|
216
|
+
if invalid_run == "identity":
|
|
217
|
+
child_path.write_text(child_path.read_text().replace("child-test-run", "replaced-run"))
|
|
218
|
+
elif invalid_run == "escalated":
|
|
219
|
+
child_path.write_text(child_path.read_text().replace(
|
|
220
|
+
"## Closure Gate\n- status: `done`", "## Closure Gate\n- status: `escalated`"))
|
|
221
|
+
return SimpleNamespace(returncode=0, pid=123, communicate=communicate)
|
|
222
|
+
monkeypatch.setattr(host.subprocess, "run", open_run)
|
|
223
|
+
monkeypatch.setattr(host.subprocess, "Popen", launch)
|
|
224
|
+
monkeypatch.setattr(host, "_state_path", lambda execution_id: root / "authority.dpapi")
|
|
225
|
+
monkeypatch.setattr(host, "_save_state", lambda path, value, **kwargs: state.update(value))
|
|
226
|
+
if invalid_run:
|
|
227
|
+
expected = "host-opened child identity" if invalid_run == "identity" else "completed child Skill run"
|
|
228
|
+
with pytest.raises(ValueError, match=expected):
|
|
229
|
+
host.dispatch_local(root, source, binary, out)
|
|
230
|
+
assert not state and not (out / "receipt.json").exists()
|
|
231
|
+
else:
|
|
232
|
+
result = host.dispatch_local(root, source, binary, out)
|
|
233
|
+
receipt = json.loads(Path(result["receipt"]).read_text())
|
|
234
|
+
verifier = HmacHumanApprovalVerifier(state["host_secret"])
|
|
235
|
+
assert validate_result(root, packet, receipt, verifier)["packet_hash"] == packet["packet_hash"]
|
|
236
|
+
provenance = json.loads((out / "dispatch.json").read_text())
|
|
237
|
+
verifier.verify_event(provenance["event"], provenance["signature"])
|
|
238
|
+
assert provenance["event"]["thread_id"] == "simulated-session"
|
|
239
|
+
assert state["host_secret"] not in (out / "receipt.json").read_text()
|
|
240
|
+
assert not (root / "knowledge/item.md").exists()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@pytest.mark.skipif(host.os.name != "nt", reason="Windows operator adapter")
|
|
244
|
+
def test_windows_dpapi_roundtrip():
|
|
245
|
+
disposable = b"non-secret disposable integration test"
|
|
246
|
+
ciphertext = host._protect(disposable)
|
|
247
|
+
assert ciphertext != disposable
|
|
248
|
+
assert host._protect(ciphertext, decrypt=True) == disposable
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Local Windows host bridge; analysis, host attestation and human action stay separate.
|
|
2
|
+
|
|
3
|
+
This is an operator-controlled adapter, not protection against another process
|
|
4
|
+
running as the same OS user. It never accepts an imported analyst receipt or an
|
|
5
|
+
automatic human-approval flag. Existing governance validators own acceptance.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import ctypes
|
|
11
|
+
import difflib
|
|
12
|
+
from datetime import date
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
import secrets
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import time
|
|
22
|
+
import uuid
|
|
23
|
+
|
|
24
|
+
from .governance_entry import apply_update, digest, revalidate, snapshot, validate_result
|
|
25
|
+
from .governance_entry_cli import _read
|
|
26
|
+
from .mcp.contribution_adoption import HmacHumanApprovalVerifier, issue_hmac_approval_assertion
|
|
27
|
+
from .skillrun import _log_field
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AppliedEvidenceError(RuntimeError):
|
|
31
|
+
"""The target changed successfully; retry must inspect reflection first."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, reflection: dict):
|
|
34
|
+
super().__init__("canonical application completed but reflection evidence save failed")
|
|
35
|
+
self.reflection = reflection
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _write_new(path: Path, value: dict) -> None:
|
|
39
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
with path.open("x", encoding="utf-8") as stream:
|
|
41
|
+
json.dump(value, stream, ensure_ascii=False, indent=2)
|
|
42
|
+
stream.write("\n")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _work_path(root: Path, path: Path) -> Path:
|
|
46
|
+
path = path.resolve()
|
|
47
|
+
if not path.is_relative_to((root / "work").resolve()):
|
|
48
|
+
raise ValueError("host evidence must remain under repository work/")
|
|
49
|
+
return path
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _relative(root: Path, path: Path) -> str:
|
|
53
|
+
return path.resolve().relative_to(root.resolve()).as_posix()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _child_environment() -> dict[str, str]:
|
|
57
|
+
# Do not pass any existing operator authority to the analyst. New host keys
|
|
58
|
+
# are generated only AFTER the analyst exits, not inherited or put on disk.
|
|
59
|
+
return {key: value for key, value in os.environ.items()
|
|
60
|
+
if not key.upper().startswith("XREFKIT_GOVERNANCE_")
|
|
61
|
+
and key.upper() != "XREFKIT_CONTRIBUTION_APPROVAL_SECRET"}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _protect(raw: bytes, *, decrypt: bool = False) -> bytes:
|
|
65
|
+
if os.name != "nt":
|
|
66
|
+
raise RuntimeError("this operator adapter requires Windows DPAPI")
|
|
67
|
+
|
|
68
|
+
class Blob(ctypes.Structure):
|
|
69
|
+
_fields_ = [("size", ctypes.c_ulong), ("data", ctypes.POINTER(ctypes.c_ubyte))]
|
|
70
|
+
|
|
71
|
+
buffer = ctypes.create_string_buffer(raw)
|
|
72
|
+
source = Blob(len(raw), ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ubyte)))
|
|
73
|
+
output = Blob()
|
|
74
|
+
crypt = ctypes.WinDLL("crypt32", use_last_error=True)
|
|
75
|
+
kernel = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
76
|
+
kernel.LocalFree.argtypes = [ctypes.c_void_p]
|
|
77
|
+
kernel.LocalFree.restype = ctypes.c_void_p
|
|
78
|
+
if decrypt:
|
|
79
|
+
success = crypt.CryptUnprotectData(ctypes.byref(source), None, None, None, None,
|
|
80
|
+
1, ctypes.byref(output))
|
|
81
|
+
else:
|
|
82
|
+
success = crypt.CryptProtectData(ctypes.byref(source), "XRefKit governance host",
|
|
83
|
+
None, None, None, 1, ctypes.byref(output))
|
|
84
|
+
if not success:
|
|
85
|
+
raise OSError("Windows authority storage failed")
|
|
86
|
+
try:
|
|
87
|
+
return ctypes.string_at(output.data, output.size)
|
|
88
|
+
finally:
|
|
89
|
+
kernel.LocalFree(output.data)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _state_path(execution_id: str) -> Path:
|
|
93
|
+
location = os.environ.get("LOCALAPPDATA")
|
|
94
|
+
if not location:
|
|
95
|
+
raise RuntimeError("LOCALAPPDATA is required for operator authority storage")
|
|
96
|
+
return Path(location) / "XRefKit" / "governance-host" / (execution_id + ".dpapi")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _save_state(path: Path, state: dict, *, create: bool = True) -> None:
|
|
100
|
+
raw = _protect(json.dumps(state, sort_keys=True).encode("utf-8"))
|
|
101
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
if create:
|
|
103
|
+
with path.open("xb") as stream:
|
|
104
|
+
stream.write(raw)
|
|
105
|
+
return
|
|
106
|
+
descriptor, temporary = tempfile.mkstemp(prefix=".authority-", dir=path.parent)
|
|
107
|
+
try:
|
|
108
|
+
with os.fdopen(descriptor, "wb") as stream:
|
|
109
|
+
stream.write(raw)
|
|
110
|
+
stream.flush()
|
|
111
|
+
os.fsync(stream.fileno())
|
|
112
|
+
os.replace(temporary, path)
|
|
113
|
+
finally:
|
|
114
|
+
if os.path.exists(temporary):
|
|
115
|
+
os.unlink(temporary)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _load_state(path: Path) -> dict:
|
|
119
|
+
state = json.loads(_protect(path.read_bytes(), decrypt=True))
|
|
120
|
+
if not isinstance(state, dict) or state.get("schema") != "xrefkit.local_host_authority/v1":
|
|
121
|
+
raise ValueError("invalid local host authority")
|
|
122
|
+
return state
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _completed_session(path: Path) -> str:
|
|
126
|
+
started = []
|
|
127
|
+
completed = 0
|
|
128
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
129
|
+
event = json.loads(line)
|
|
130
|
+
if event.get("type") in {"error", "turn.failed"}:
|
|
131
|
+
raise ValueError("analyst stream reported failure")
|
|
132
|
+
if event.get("type") == "thread.started":
|
|
133
|
+
started.append(event.get("thread_id"))
|
|
134
|
+
if event.get("type") == "turn.completed":
|
|
135
|
+
completed += 1
|
|
136
|
+
if len(started) != 1 or not isinstance(started[0], str) or not started[0] or completed != 1:
|
|
137
|
+
raise ValueError("one actual completed Codex thread is required")
|
|
138
|
+
return started[0]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _prompt(packet_path: Path, child_log: Path, result: Path) -> str:
|
|
142
|
+
return f"""Act only as the isolated shared_asset_update_gate analyst.
|
|
143
|
+
The host has already opened your correlated Skill run. Do not open another run.
|
|
144
|
+
Read the repository startup sources, then the selected method
|
|
145
|
+
skills/os/shared_asset_update_gate/SKILL.v1.md and the immutable packet:
|
|
146
|
+
{packet_path}
|
|
147
|
+
Your opened run log: {child_log}
|
|
148
|
+
Save the strict JSON result body (without output pointer) to: {result}
|
|
149
|
+
Consume only packet materials and required startup/method contracts. Do not edit
|
|
150
|
+
the packet, candidate, canonical target, specialist evidence, parent log or code.
|
|
151
|
+
Use the existing skill workitem/artifact/concern/phase/verify/close commands to
|
|
152
|
+
record and close your own run. Never advance check yourself; use deterministic
|
|
153
|
+
verify. Never sign a receipt or approve/apply a target. Record the designated
|
|
154
|
+
result as a done output artifact using its repository-relative path; record
|
|
155
|
+
evidence, explicit handoff and all concerns. Report failed/unknown findings
|
|
156
|
+
honestly. Successful process exit is not evidence that checks pass.
|
|
157
|
+
The later host attestation, separate human action and canonical application are
|
|
158
|
+
normal downstream responsibilities, outside your analysis scope. Record them
|
|
159
|
+
as handoff steps; their being future steps alone is not an unresolved analysis
|
|
160
|
+
risk. If you identify a concrete unresolved risk or uncertainty, retain it and
|
|
161
|
+
its required disposition even when that prevents host attestation. The host
|
|
162
|
+
requires actual Closure Gate done; it will not reinterpret escalated closure as
|
|
163
|
+
success, and you must not resolve a genuine concern merely to obtain a receipt.
|
|
164
|
+
Return only a short completion message after the output and run are saved.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def dispatch_local(root: Path, packet_path: Path, codex: Path, out_dir: Path,
|
|
169
|
+
*, timeout: int = 1800) -> dict:
|
|
170
|
+
"""Run the actual CLI child, then attest its validated, closed result."""
|
|
171
|
+
root = root.resolve()
|
|
172
|
+
packet_path = packet_path.resolve()
|
|
173
|
+
packet = _read(str(packet_path))
|
|
174
|
+
revalidate(root, packet)
|
|
175
|
+
if packet["kind"] != "asset_update" or "endpoint" in packet["payload"]:
|
|
176
|
+
raise ValueError("local host requires a canonical single-target asset_update packet")
|
|
177
|
+
codex = codex.resolve(strict=True)
|
|
178
|
+
if not codex.is_file() or codex.suffix.lower() != ".exe":
|
|
179
|
+
raise ValueError("select the trusted native Codex executable, not a shell wrapper")
|
|
180
|
+
out_dir = _work_path(root, out_dir)
|
|
181
|
+
out_dir.mkdir(parents=True, exist_ok=False)
|
|
182
|
+
child = out_dir / f"{date.today().isoformat()}_skill_run_shared_asset_update_gate.md"
|
|
183
|
+
output = out_dir / "analysis.json"
|
|
184
|
+
final = out_dir / "last-message.txt"
|
|
185
|
+
events = out_dir / "events.jsonl"
|
|
186
|
+
stderr = out_dir / "stderr.log"
|
|
187
|
+
binding = packet["binding"]
|
|
188
|
+
command = [sys.executable, "-m", "xrefkit", "skill", "run", "--root", str(root),
|
|
189
|
+
"--definition", "skills/os/shared_asset_update_gate/SKILL.v1.md",
|
|
190
|
+
"--task", "Analyze frozen canonical governance packet " + packet["packet_hash"],
|
|
191
|
+
"--capability", "governance analysis", "--tuning", "shared asset management",
|
|
192
|
+
"--responsibility", "Analyze only the frozen common management checks without adoption",
|
|
193
|
+
"--execution-mode", "subagent_required", "--parent-run-id", binding["run_id"],
|
|
194
|
+
"--root-run-id", binding["run_snapshot"]["fields"]["root_run_id"],
|
|
195
|
+
"--work-item-id", binding["work_item_id"], "--out", str(child), "--json"]
|
|
196
|
+
opened = subprocess.run(command, cwd=root, env=_child_environment(), capture_output=True,
|
|
197
|
+
text=True, encoding="utf-8", timeout=60, check=False)
|
|
198
|
+
(out_dir / "open-run.json").write_text(opened.stdout, encoding="utf-8")
|
|
199
|
+
opened_record = json.loads(opened.stdout)
|
|
200
|
+
if opened.returncode or not opened_record.get("ok") or not opened_record.get("run_id"):
|
|
201
|
+
raise ValueError("analyst Skill run refused; inspect open-run.json")
|
|
202
|
+
execution_id = str(uuid.uuid4())
|
|
203
|
+
argv = [str(codex), "exec", "--json", "--color", "never", "--sandbox", "workspace-write",
|
|
204
|
+
"--cd", str(root), "--output-last-message", str(final), "-"]
|
|
205
|
+
before = time.time()
|
|
206
|
+
with events.open("xb") as stdout, stderr.open("xb") as error:
|
|
207
|
+
process = subprocess.Popen(argv, cwd=root, env=_child_environment(), stdin=subprocess.PIPE,
|
|
208
|
+
stdout=stdout, stderr=error)
|
|
209
|
+
try:
|
|
210
|
+
process.communicate(_prompt(packet_path, child, output).encode("utf-8"), timeout=timeout)
|
|
211
|
+
except subprocess.TimeoutExpired:
|
|
212
|
+
if os.name == "nt":
|
|
213
|
+
subprocess.run(["taskkill.exe", "/PID", str(process.pid), "/T", "/F"],
|
|
214
|
+
capture_output=True, timeout=30, check=False)
|
|
215
|
+
process.kill()
|
|
216
|
+
process.wait()
|
|
217
|
+
raise ValueError("analyst timed out; no attestation or approval created") from None
|
|
218
|
+
if process.returncode:
|
|
219
|
+
raise ValueError("analyst process failed; inspect stderr.log")
|
|
220
|
+
session = _completed_session(events)
|
|
221
|
+
if not final.is_file() or not final.read_text(encoding="utf-8").strip():
|
|
222
|
+
raise ValueError("analyst final message missing")
|
|
223
|
+
revalidate(root, packet)
|
|
224
|
+
if _log_field(child.read_text(encoding="utf-8"), "run_id") != opened_record["run_id"]:
|
|
225
|
+
raise ValueError("analyst replaced the host-opened child identity")
|
|
226
|
+
body = _read(str(output))
|
|
227
|
+
if "output" in body:
|
|
228
|
+
raise ValueError("analyst must not supply a host output pointer")
|
|
229
|
+
result = {**body, "output": snapshot(root, _relative(root, output))}
|
|
230
|
+
event = {"schema": "xrefkit.governance_execution/v1", "packet_hash": packet["packet_hash"],
|
|
231
|
+
"execution_id": execution_id, "child_log": _relative(root, child),
|
|
232
|
+
"child_log_hash": snapshot(root, _relative(root, child))["sha256"],
|
|
233
|
+
"skill_id": "shared_asset_update_gate", "result": result, "result_hash": digest(result)}
|
|
234
|
+
host_secret = secrets.token_hex(32)
|
|
235
|
+
verifier = HmacHumanApprovalVerifier(host_secret)
|
|
236
|
+
receipt = {"event": event, "signature": verifier.seal_event(event)}
|
|
237
|
+
validate_result(root, packet, receipt, verifier)
|
|
238
|
+
state_path = _state_path(execution_id)
|
|
239
|
+
provenance = {"schema": "xrefkit.local_host_dispatch/v1", "execution_id": execution_id,
|
|
240
|
+
"packet_hash": packet["packet_hash"], "thread_id": session, "pid": process.pid,
|
|
241
|
+
"started_at": before, "completed_at": time.time(), "exit_code": process.returncode,
|
|
242
|
+
"executable": str(codex), "executable_sha256": hashlib.sha256(codex.read_bytes()).hexdigest(),
|
|
243
|
+
"events": snapshot(root, _relative(root, events)), "receipt_hash": digest(receipt)}
|
|
244
|
+
_save_state(state_path, {"schema": "xrefkit.local_host_authority/v1", "root": str(root),
|
|
245
|
+
"packet_hash": packet["packet_hash"], "receipt_hash": digest(receipt),
|
|
246
|
+
"host_secret": host_secret, "applied": False})
|
|
247
|
+
_write_new(out_dir / "receipt.json", receipt)
|
|
248
|
+
_write_new(out_dir / "dispatch.json", {"event": provenance, "signature": verifier.seal_event(provenance)})
|
|
249
|
+
handoff = {"receipt": str(out_dir / "receipt.json"), "authority_state": str(state_path),
|
|
250
|
+
"status": "dispatched_not_approved", "human_action": "run apply and review the native dialog"}
|
|
251
|
+
_write_new(out_dir / "handoff.json", handoff)
|
|
252
|
+
return handoff
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _review_dialog(target: str, candidate_hash: str, difference: str) -> bool:
|
|
256
|
+
# No command-line/STDIN yes path. The operator must act in this native UI.
|
|
257
|
+
import tkinter as tk
|
|
258
|
+
from tkinter.scrolledtext import ScrolledText
|
|
259
|
+
|
|
260
|
+
window = tk.Tk()
|
|
261
|
+
window.title("XRefKit — 正式Skillソースへの反映")
|
|
262
|
+
window.geometry("1000x750")
|
|
263
|
+
accepted = False
|
|
264
|
+
tk.Label(window, text="次の差分を正式ソースへ反映します。成熟度・採用・配布版は変更しません。",
|
|
265
|
+
anchor="w").pack(fill="x", padx=12, pady=8)
|
|
266
|
+
tk.Label(window, text=f"対象: {target}\n候補 SHA-256: {candidate_hash}",
|
|
267
|
+
anchor="w", justify="left", wraplength=970).pack(fill="x", padx=12)
|
|
268
|
+
text = ScrolledText(window, wrap="none")
|
|
269
|
+
text.insert("1.0", difference)
|
|
270
|
+
text.configure(state="disabled")
|
|
271
|
+
text.pack(fill="both", expand=True, padx=12, pady=12)
|
|
272
|
+
|
|
273
|
+
def approve() -> None:
|
|
274
|
+
nonlocal accepted
|
|
275
|
+
accepted = True
|
|
276
|
+
window.destroy()
|
|
277
|
+
|
|
278
|
+
buttons = tk.Frame(window)
|
|
279
|
+
buttons.pack(fill="x", padx=12, pady=10)
|
|
280
|
+
tk.Button(buttons, text="反映せず終了", command=window.destroy).pack(side="right", padx=8)
|
|
281
|
+
tk.Button(buttons, text="この差分を正式ソースへ反映", command=approve).pack(side="right", padx=8)
|
|
282
|
+
window.mainloop()
|
|
283
|
+
return accepted
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def apply_local(root: Path, packet_path: Path, receipt_path: Path, state_path: Path,
|
|
287
|
+
output: Path) -> dict:
|
|
288
|
+
root = root.resolve()
|
|
289
|
+
packet = _read(str(packet_path))
|
|
290
|
+
receipt = _read(str(receipt_path))
|
|
291
|
+
state = _load_state(state_path)
|
|
292
|
+
if (state.get("root") != str(root) or state.get("packet_hash") != packet["packet_hash"]
|
|
293
|
+
or state.get("receipt_hash") != digest(receipt) or state.get("applied")):
|
|
294
|
+
raise ValueError("authority state does not match this unapplied dispatch")
|
|
295
|
+
verifier = HmacHumanApprovalVerifier(state["host_secret"])
|
|
296
|
+
result = validate_result(root, packet, receipt, verifier)
|
|
297
|
+
if result["unknowns"] or any(row["result"] not in {"pass", "not_applicable"} for row in result["findings"]):
|
|
298
|
+
raise ValueError("failed or unknown analysis cannot be approved")
|
|
299
|
+
output = _work_path(root, output)
|
|
300
|
+
protected = {packet_path.resolve(), receipt_path.resolve(), state_path.resolve(),
|
|
301
|
+
(root / packet["log"]).resolve(), (root / packet["payload"]["target"]).resolve(),
|
|
302
|
+
(root / receipt["event"]["child_log"]).resolve(),
|
|
303
|
+
*((root / row["path"]).resolve() for row in packet["materials"]),
|
|
304
|
+
(root / result["output"]["path"]).resolve()}
|
|
305
|
+
if output in protected:
|
|
306
|
+
raise ValueError("reflection output would overwrite governance input")
|
|
307
|
+
_write_new(output, {"status": "awaiting_native_human_action"})
|
|
308
|
+
target = packet["payload"]["target"]
|
|
309
|
+
old_bytes = (root / target).read_bytes() if (root / target).exists() else None
|
|
310
|
+
candidate = packet["payload"]["candidate"]
|
|
311
|
+
new_bytes = (root / candidate).read_bytes()
|
|
312
|
+
candidate_hash = next(row["sha256"] for row in packet["materials"] if row["path"] == candidate)
|
|
313
|
+
if (hashlib.sha256(new_bytes).hexdigest() != candidate_hash
|
|
314
|
+
or (hashlib.sha256(old_bytes).hexdigest() if old_bytes is not None else None)
|
|
315
|
+
!= packet["payload"]["expected_target_hash"]):
|
|
316
|
+
raise ValueError("review display differs from frozen candidate or target")
|
|
317
|
+
revalidate(root, packet)
|
|
318
|
+
old = old_bytes.decode("utf-8") if old_bytes is not None else ""
|
|
319
|
+
new = new_bytes.decode("utf-8")
|
|
320
|
+
difference = "".join(difflib.unified_diff(old.splitlines(True), new.splitlines(True),
|
|
321
|
+
fromfile=target, tofile=candidate))
|
|
322
|
+
if not _review_dialog(target, candidate_hash, difference):
|
|
323
|
+
output.write_text('{"status":"human_declined_no_application"}\n', encoding="utf-8")
|
|
324
|
+
return {"status": "human_declined_no_application"}
|
|
325
|
+
# Authority is issued only after the native action, in the operator process.
|
|
326
|
+
# Revalidation inside apply_update rejects changes made while the UI was open.
|
|
327
|
+
human_secret = secrets.token_hex(32)
|
|
328
|
+
assertion = issue_hmac_approval_assertion(human_secret, {
|
|
329
|
+
"action": "apply_governed_asset", "packet_hash": packet["packet_hash"],
|
|
330
|
+
"target": target, "result_hash": digest(result)})
|
|
331
|
+
reflected = apply_update(root, packet, receipt, host_verifier=verifier,
|
|
332
|
+
approval_verifier=HmacHumanApprovalVerifier(human_secret),
|
|
333
|
+
approval_assertion=assertion)
|
|
334
|
+
state.update({"human_secret": human_secret, "approval_assertion": assertion, "applied": True,
|
|
335
|
+
"approved_at": time.time(), "reflected_target": reflected["target"]})
|
|
336
|
+
try:
|
|
337
|
+
_save_state(state_path, state, create=False)
|
|
338
|
+
output.write_text(json.dumps(reflected, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
339
|
+
except (OSError, RuntimeError) as exc:
|
|
340
|
+
raise AppliedEvidenceError(reflected) from exc
|
|
341
|
+
return reflected
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def main(argv=None) -> int:
|
|
345
|
+
parser = argparse.ArgumentParser(prog="python -m xrefkit.governance_host")
|
|
346
|
+
parser.add_argument("--root", required=True, type=Path)
|
|
347
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
348
|
+
start = commands.add_parser("dispatch")
|
|
349
|
+
start.add_argument("--packet", required=True, type=Path)
|
|
350
|
+
start.add_argument("--codex", type=Path, default=os.environ.get("CODEX_APP_RUNTIME_CLI_PATH"))
|
|
351
|
+
start.add_argument("--out-dir", required=True, type=Path)
|
|
352
|
+
start.add_argument("--timeout", type=int, default=1800)
|
|
353
|
+
apply = commands.add_parser("apply")
|
|
354
|
+
apply.add_argument("--packet", required=True, type=Path)
|
|
355
|
+
apply.add_argument("--receipt", required=True, type=Path)
|
|
356
|
+
apply.add_argument("--authority-state", required=True, type=Path)
|
|
357
|
+
apply.add_argument("--out", required=True, type=Path)
|
|
358
|
+
args = parser.parse_args(argv)
|
|
359
|
+
try:
|
|
360
|
+
if args.command == "dispatch":
|
|
361
|
+
if args.codex is None or args.timeout <= 0:
|
|
362
|
+
raise ValueError("native Codex executable and positive timeout required")
|
|
363
|
+
result = dispatch_local(args.root, args.packet, args.codex, args.out_dir, timeout=args.timeout)
|
|
364
|
+
else:
|
|
365
|
+
result = apply_local(args.root, args.packet, args.receipt, args.authority_state, args.out)
|
|
366
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
367
|
+
return 0
|
|
368
|
+
except AppliedEvidenceError as exc:
|
|
369
|
+
print(str(exc))
|
|
370
|
+
print(json.dumps(exc.reflection, ensure_ascii=False))
|
|
371
|
+
return 3
|
|
372
|
+
except (ValueError, OSError, RuntimeError, KeyError, TypeError, subprocess.SubprocessError) as exc:
|
|
373
|
+
print(f"local governance host refused: {exc}")
|
|
374
|
+
return 2
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if __name__ == "__main__":
|
|
378
|
+
raise SystemExit(main())
|