xrefkit 0.7.6__tar.gz → 0.7.8__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.8}/PKG-INFO +1 -1
- {xrefkit-0.7.6 → xrefkit-0.7.8}/pyproject.toml +1 -1
- xrefkit-0.7.8/tests/test_governance_host.py +248 -0
- xrefkit-0.7.8/tests/test_package_knowledge_resolution.py +166 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_package_skill_run.py +29 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/__init__.py +1 -1
- xrefkit-0.7.8/xrefkit/governance_host.py +378 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/catalog.py +113 -7
- {xrefkit-0.7.6 → xrefkit-0.7.8/xrefkit.egg-info}/PKG-INFO +1 -1
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit.egg-info/SOURCES.txt +3 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/LICENSE +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/README.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/setup.cfg +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_artifact_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_attention_pet.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_attention_pet_client_protocol.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_attention_pet_codex_session.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_attention_pet_fit.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_azure_connection.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_azure_import.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_azure_recovery.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_azure_update_candidate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_azure_writer.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_boundary_analysis.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_brownfield_csharp_structure_definitions.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_brownfield_file_editing_protocol.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_business_intake_conversation_definitions.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_business_intake_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_calibration_lint.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_catalog_preparation_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_check_skill_knowledge_xids.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_ci_workflow_invariants.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_code_constraint_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_collect_analyzer_sarif.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_compact_skill_logs.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_constraint_derivation_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_constraint_derivation_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_contribution_adoption_bytes.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_contribution_returns.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_convert_to_xrefkit_skill.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_cs_scope_probe.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_csharp_commonality.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_csharp_naming_profile.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_ctx.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_cutover_readiness.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_dashboard.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_database_batch_definition_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_decision_trace.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_design_business_intake_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_design_planning_definition_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_dotnet_definition_candidate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_editorial_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_editorial_intake_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_editorial_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_embedded_startup_pack.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_error_policy_audit.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_error_policy_locator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_execution_binding.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_existing_skill_definition_batch.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_final_skilldefinition_batch.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_flow_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_fm_multiroot.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_gate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_gateway.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_gateway_mcp.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_gateway_skill_adapter.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_gateway_work_items.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_goal_desired_state.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_governance_entry.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_host_precheck.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_human_evaluation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_implementation_flow_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_implementation_review_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_inbound_uploads.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_instruction_workflow.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_knowledge_relations_validator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_language_review_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_legacy_guidance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_legacy_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_legacy_resources.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_mcp_package_routing.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_mcp_setup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_mcp_subagent_startup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_mcp_subagent_startup_integration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_ontology_assessment_validator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_os_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_os_knowledge_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_os_selected_family.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_ownership.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_packaged_native_governance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_packmeta.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_plan_markdown.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_plan_observation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_project_management_materialization.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_project_quality_baseline.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_qa_report_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_reporting_profiles.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_reporting_quality.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_repository_skill_adoption.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_repository_startup_pack.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_resource_provider.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_runtime_contracts.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_sarif_to_locator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_security_definition_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_site_release_metadata.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skill_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skill_definition_governance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skill_definition_mcp.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skill_definition_runtime.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skill_edits.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skill_maturity_return.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skill_release_compatibility.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skill_runtime_audit.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skillmeta.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_skills_sync.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_source_analysis_fragments.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_structure_catalog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_subagent_startup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_subagent_startup_boundaries.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_wbs.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_work_management.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_work_packages.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_workflow_family_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_xref.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_xrefkit_instance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_xrefkit_tools.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_xrefkit_v2_discovery.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_xrefkit_v2_models.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/tests/test_xrefkit_v2_pipeline.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/__main__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/__init__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/__main__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/client.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/client_protocol.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/codex_session.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/evaluator.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/model.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/presentation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/profiles.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/scenarios.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/server.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/attention_pet/store.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/azure_connection.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/azure_import.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/azure_recovery.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/azure_update_candidate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/azure_writer.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/boundary_analysis.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/catalog_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/contracts.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/ctx.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/dashboard.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/decision_trace.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/discovery.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/execution_binding.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/execution_binding_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/gate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/gateway.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/goalstate.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/governance_entry.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/governance_entry_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/hashing.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/host_precheck.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/import_skill.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/instance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/loaders.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/__init__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/audit.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/bootstrap.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/client_cache.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/client_flow.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/context_registry.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/context_token.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/contracts.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/contribution_adoption.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/contribution_returns.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/dist.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/gateway.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/governed_overlays.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/inbound_uploads.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/knowledge_edits.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/legacy_guidance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/legacy_migration.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/legacy_resources.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/ownership.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/repository.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/schemas.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/server.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/setup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/skill_edits.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/skill_maturity.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/startup_contract_pack.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/subagent_startup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp/update_gate_intake.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/mcp_tools.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/__init__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/common.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/effective_bundle.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/human_evaluation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/local_manifest.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/package_manifest.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/run_log.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/server_config.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/models/skill_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/operations_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/ownership.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/packmeta.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/plan_markdown.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/plan_observation.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/registry.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/reporting.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/reporting_profiles.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/repository_skills.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resolver.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resource_provider.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/attention_pet/client-state.schema.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/attention_pet/pet.css +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/attention_pet/pet.html +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/attention_pet/pet.js +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/contracts.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/current.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/model_body.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/startup_sources/000_agent_entry.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/startup_sources/011_startup_xref_routing.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/startup_sources/015_shared_memory_operations.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/startup_sources/016_uncertainty_protocol.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/startup_sources/017_base_and_xref_layering.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/startup_sources/053_context_direction_security_guard.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/base/startup_sources/yomiyasu_LICENSE.txt +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/migration/docs/guides/062_legacy_flow_skill_migration_guide.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/migration/knowledge/operations/130_legacy_flow_skill_migration_rules.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/migration/knowledge/organization/160_context_direction_guard_rules.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/migration/skills/os/legacy_flow_skill_migration/SKILL.v1.md +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/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.8}/xrefkit/resources/migration/skills/repository_adoption.json +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/resources/migration/tools/migrate_legacy_flow_skill.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/runlog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/skill_definition.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/skill_definition_catalog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/skill_definition_governance.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/skillmeta.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/skillrun.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/skills_sync.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/structure_catalog.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/subagent_startup.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/tools/__init__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/tools/__main__.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/v2_cli.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/wbs.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/work_management.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/work_management_view.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/workspace.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit/xref.py +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit.egg-info/dependency_links.txt +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit.egg-info/entry_points.txt +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/xrefkit.egg-info/requires.txt +0 -0
- {xrefkit-0.7.6 → xrefkit-0.7.8}/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,166 @@
|
|
|
1
|
+
from dataclasses import replace
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from xrefkit.discovery import DiscoveredSkillPackage
|
|
6
|
+
from xrefkit.loaders import load_package_manifest
|
|
7
|
+
from xrefkit.mcp.catalog import XRefCatalog
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def package_catalog(tmp_path, asset_path="knowledge/rules.md"):
|
|
11
|
+
import yaml
|
|
12
|
+
package_root = tmp_path / "package"
|
|
13
|
+
package_root.mkdir()
|
|
14
|
+
(package_root / "knowledge").mkdir()
|
|
15
|
+
(package_root / "knowledge/rules.md").write_text("# Package rules\n\nRequired rules.\n")
|
|
16
|
+
(package_root / "method.md").write_text("# Method\n")
|
|
17
|
+
manifest = package_root / "package_manifest.yaml"
|
|
18
|
+
manifest.write_text(yaml.safe_dump({
|
|
19
|
+
"package_id": "example.rules", "package_type": "skill_package", "version": "1.0.0",
|
|
20
|
+
"requires": {"xrefkit_core": ">=2.0.0 <3.0.0"},
|
|
21
|
+
"provides": {
|
|
22
|
+
"skills": [{"id": "example.review", "xid": "xid-skill-example", "path": "method.md"}],
|
|
23
|
+
"knowledge": [{"id": "rules", "xid": "xid-knowledge-rules", "path": asset_path}],
|
|
24
|
+
},
|
|
25
|
+
"contract": {},
|
|
26
|
+
}))
|
|
27
|
+
package = DiscoveredSkillPackage("example", package_root, manifest, load_package_manifest(manifest))
|
|
28
|
+
repo = tmp_path / "repo"
|
|
29
|
+
repo.mkdir()
|
|
30
|
+
return replace(XRefCatalog.build(repo), discovered_packages=(package,))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_manifest_identity_resolves_without_inline_xid(tmp_path):
|
|
34
|
+
catalog = package_catalog(tmp_path)
|
|
35
|
+
document = catalog.get_document_by_xid("xid-knowledge-rules")
|
|
36
|
+
assert document["content"] == catalog.expand_knowledge("xid-knowledge-rules")["content"]
|
|
37
|
+
assert document["xid"] == "xid-knowledge-rules"
|
|
38
|
+
assert catalog.search_knowledge_catalog("Required rules")[0]["xid"] == "xid-knowledge-rules"
|
|
39
|
+
cached = catalog.get_document_by_xid("xid-knowledge-rules", document["content_hash"])
|
|
40
|
+
assert cached["content_hash"] == document["content_hash"]
|
|
41
|
+
assert cached["content_omitted"] == document["cache_policy"]["cache_recommended"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_duplicate_package_xid_refuses_selection(tmp_path):
|
|
45
|
+
catalog = package_catalog(tmp_path)
|
|
46
|
+
catalog = replace(catalog, discovered_packages=catalog.discovered_packages * 2)
|
|
47
|
+
assert catalog.get_document_by_xid("xid-knowledge-rules")["error"] == "xid_conflict"
|
|
48
|
+
with pytest.raises(ValueError, match="knowledge xid conflict"):
|
|
49
|
+
catalog.expand_knowledge("xid-knowledge-rules")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_package_path_cannot_escape_root(tmp_path):
|
|
53
|
+
catalog = package_catalog(tmp_path, "../outside.md")
|
|
54
|
+
with pytest.raises(ValueError, match="escapes root"):
|
|
55
|
+
catalog.get_document_by_xid("xid-knowledge-rules")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def installed_catalog(tmp_path, monkeypatch):
|
|
59
|
+
"""Simulate entry-point installation, then use the real package discovery loader."""
|
|
60
|
+
import yaml
|
|
61
|
+
from types import SimpleNamespace
|
|
62
|
+
from xrefkit import discovery
|
|
63
|
+
catalog = package_catalog(tmp_path)
|
|
64
|
+
package = catalog.discovered_packages[0]
|
|
65
|
+
root = package.package_root
|
|
66
|
+
manifest = yaml.safe_load(package.manifest_path.read_text())
|
|
67
|
+
manifest["provides"]["skills"][0]["path"] = "method.skill.yaml"
|
|
68
|
+
manifest["provides"]["skills"][0]["required_knowledge"] = ["xid-knowledge-rules"]
|
|
69
|
+
manifest["provides"]["review_axes"] = [{"id": "axis", "xid": "xid-axis-rules", "path": "axis.yaml"}]
|
|
70
|
+
manifest["provides"]["schemas"] = [{"id": "schema", "xid": "xid-schema-rules", "path": "schema.json"}]
|
|
71
|
+
package.manifest_path.write_text(yaml.safe_dump(manifest))
|
|
72
|
+
(root / "method.skill.yaml").write_text(yaml.safe_dump({
|
|
73
|
+
"skill_id": "example.review", "xid": "xid-skill-example",
|
|
74
|
+
"entry": {"xid": "xid-entry-example", "path": "method.md", "load_policy": "required_inline"},
|
|
75
|
+
"required_outputs": ["report"], "required_knowledge": ["xid-knowledge-rules"],
|
|
76
|
+
"review_axes": ["xid-axis-rules"], "schemas": ["xid-schema-rules"],
|
|
77
|
+
"must_not": [], "extension_policy": {},
|
|
78
|
+
}))
|
|
79
|
+
(root / "axis.yaml").write_text("axis_id: example\nxid: xid-axis-rules\nchecks: []\n")
|
|
80
|
+
(root / "schema.json").write_text('{"type":"object"}\n')
|
|
81
|
+
ep = SimpleNamespace(name="example", load=lambda: lambda: root)
|
|
82
|
+
monkeypatch.setattr(discovery, "_select_entry_points", lambda group: [ep])
|
|
83
|
+
return XRefCatalog.build(catalog.repo_root, discover_packages=True)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_discovered_required_knowledge_is_resolved_and_all_declared_assets_are_fetchable(tmp_path, monkeypatch):
|
|
87
|
+
catalog = installed_catalog(tmp_path, monkeypatch)
|
|
88
|
+
skill = catalog.get_skill("example.review")
|
|
89
|
+
assert skill["required_knowledge"] == [{"xid": "xid-knowledge-rules", "required": True, "reason": "package declaration"}]
|
|
90
|
+
resolved = catalog.resolve_skill_knowledge("example.review")
|
|
91
|
+
assert not resolved["unsatisfied_required"]
|
|
92
|
+
assert resolved["slots"][0]["bind"] == "xid-knowledge-rules"
|
|
93
|
+
assert resolved["slots"][0]["required"] is True and resolved["slots"][0]["satisfied"] is True
|
|
94
|
+
for xid in ("xid-knowledge-rules", "xid-axis-rules", "xid-schema-rules", "xid-entry-example", "xid-skill-example"):
|
|
95
|
+
assert catalog.get_document_by_xid(xid)["content"]
|
|
96
|
+
assert catalog.expand_knowledge("xid-knowledge-rules")["content"].startswith("# Package rules")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_undeclared_file_is_not_exposed_and_absent_need_remains_unsatisfied(tmp_path, monkeypatch):
|
|
100
|
+
catalog = installed_catalog(tmp_path, monkeypatch)
|
|
101
|
+
package = catalog.discovered_packages[0]
|
|
102
|
+
(package.package_root / "secret.md").write_text("<!-- xid: ABCDEF123456 -->\n# Never declared")
|
|
103
|
+
with pytest.raises(KeyError):
|
|
104
|
+
catalog.get_document_by_xid("ABCDEF123456")
|
|
105
|
+
package.manifest.provides.knowledge.clear()
|
|
106
|
+
resolved = catalog.resolve_skill_knowledge("example.review")
|
|
107
|
+
assert resolved["unsatisfied_required"] == ["xid-knowledge-rules"]
|
|
108
|
+
assert resolved["slots"][0]["candidates"] == []
|
|
109
|
+
with pytest.raises(KeyError):
|
|
110
|
+
catalog.expand_knowledge("xid-knowledge-rules")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_live_asset_changes_invalidate_catalog_and_document_cache(tmp_path, monkeypatch):
|
|
114
|
+
catalog = installed_catalog(tmp_path, monkeypatch)
|
|
115
|
+
path = catalog.discovered_packages[0].package_root / "axis.yaml"
|
|
116
|
+
before = catalog.catalog_version
|
|
117
|
+
document = catalog.get_document_by_xid("xid-axis-rules")
|
|
118
|
+
path.write_text(path.read_text() + "summary: revised\n")
|
|
119
|
+
current = catalog.get_document_by_xid("xid-axis-rules", document["content_hash"])
|
|
120
|
+
assert not current["content_omitted"]
|
|
121
|
+
assert current["content_hash"] != document["content_hash"]
|
|
122
|
+
assert catalog.catalog_version != before
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_duplicate_knowledge_cannot_silently_satisfy_required_slot(tmp_path, monkeypatch):
|
|
126
|
+
catalog = installed_catalog(tmp_path, monkeypatch)
|
|
127
|
+
(catalog.repo_root / "knowledge").mkdir()
|
|
128
|
+
(catalog.repo_root / "knowledge/local.md").write_text("<!-- xid: xid-knowledge-rules -->\n# Conflicting local rules")
|
|
129
|
+
with pytest.raises(ValueError, match="knowledge xid conflict"):
|
|
130
|
+
catalog.resolve_skill_knowledge("example.review")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@pytest.mark.parametrize("relative", ["../outside.md", "/absolute.md", "C:/absolute.md", "knowledge/../rules.md", "knowledge\\rules.md"])
|
|
134
|
+
def test_nonportable_or_escaping_declarations_refused(tmp_path, relative):
|
|
135
|
+
catalog = package_catalog(tmp_path, relative)
|
|
136
|
+
with pytest.raises(ValueError, match="package asset escapes root"):
|
|
137
|
+
catalog.get_document_by_xid("xid-knowledge-rules")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_symlink_escape_is_not_exposed(tmp_path):
|
|
141
|
+
catalog = package_catalog(tmp_path)
|
|
142
|
+
outside = tmp_path / "outside.md"
|
|
143
|
+
outside.write_text("private outside content")
|
|
144
|
+
inside = catalog.discovered_packages[0].package_root / "knowledge/rules.md"
|
|
145
|
+
inside.unlink()
|
|
146
|
+
try:
|
|
147
|
+
inside.symlink_to(outside)
|
|
148
|
+
except OSError:
|
|
149
|
+
pytest.skip("symlink creation not available")
|
|
150
|
+
with pytest.raises(ValueError, match="escapes root"):
|
|
151
|
+
catalog.get_document_by_xid("xid-knowledge-rules")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_package_cannot_shadow_embedded_startup_identity(tmp_path):
|
|
155
|
+
catalog = package_catalog(tmp_path)
|
|
156
|
+
catalog.discovered_packages[0].manifest.provides.knowledge[0].xid = "0B5C58B5E5B2"
|
|
157
|
+
with pytest.raises(ValueError, match="core startup XID"):
|
|
158
|
+
catalog.get_document_by_xid("0B5C58B5E5B2")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_inline_identity_mismatch_refused(tmp_path):
|
|
162
|
+
catalog = package_catalog(tmp_path)
|
|
163
|
+
path = catalog.discovered_packages[0].package_root / "knowledge/rules.md"
|
|
164
|
+
path.write_text("<!-- xid: ABCDEF123456 -->\n# Wrong identity")
|
|
165
|
+
with pytest.raises(ValueError, match="identity does not match"):
|
|
166
|
+
catalog.expand_knowledge("xid-knowledge-rules")
|
|
@@ -18,6 +18,13 @@ def test_installed_package_run_binds_over_mcp(tmp_path, module, reference, xid):
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
20
|
pytest.importorskip(module)
|
|
21
|
+
from xrefkit.discovery import discover_skill_packages
|
|
22
|
+
package_id = reference.split("::")[0]
|
|
23
|
+
package = next(item for item in discover_skill_packages() if item.package_id == package_id)
|
|
24
|
+
package_knowledge_xids = [asset.xid for asset in package.manifest.provides.knowledge]
|
|
25
|
+
package_reference_xids = [asset.xid for asset in (
|
|
26
|
+
*package.manifest.provides.review_axes, *package.manifest.provides.schemas
|
|
27
|
+
)]
|
|
21
28
|
anyio = pytest.importorskip("anyio")
|
|
22
29
|
pytest.importorskip("mcp")
|
|
23
30
|
from mcp.client.session import ClientSession
|
|
@@ -94,8 +101,30 @@ def test_installed_package_run_binds_over_mcp(tmp_path, module, reference, xid):
|
|
|
94
101
|
)
|
|
95
102
|
assert not requirements.isError, requirements.content
|
|
96
103
|
assert not requirements.structuredContent["missing"]
|
|
104
|
+
for xid in package_knowledge_xids:
|
|
105
|
+
document = await session.call_tool("get_document_by_xid", {"xid": xid})
|
|
106
|
+
assert not document.isError, document.content
|
|
107
|
+
expanded = await session.call_tool("expand_knowledge", {"xid": xid})
|
|
108
|
+
assert not expanded.isError, expanded.content
|
|
109
|
+
assert expanded.structuredContent["content"]
|
|
110
|
+
|
|
97
111
|
skill = await session.call_tool("get_skill", {"skill_id": run["skill_id"]})
|
|
98
112
|
assert not skill.isError, skill.content
|
|
99
113
|
assert skill.structuredContent["skill_id"] == run["skill_id"]
|
|
114
|
+
for asset_xid in package_reference_xids:
|
|
115
|
+
document = await session.call_tool("get_document_by_xid", {"xid": asset_xid})
|
|
116
|
+
assert not document.isError, document.content
|
|
117
|
+
assert document.structuredContent["content"]
|
|
118
|
+
resolved = await session.call_tool(
|
|
119
|
+
"resolve_skill_knowledge", {"skill_id": run["skill_id"]}
|
|
120
|
+
)
|
|
121
|
+
assert not resolved.isError, resolved.content
|
|
122
|
+
required_xids = {need["xid"] for need in skill.structuredContent["required_knowledge"]}
|
|
123
|
+
assert required_xids <= {slot["bind"] for slot in resolved.structuredContent["slots"]}
|
|
124
|
+
for slot in resolved.structuredContent["slots"]:
|
|
125
|
+
if slot["bind"] in package_knowledge_xids:
|
|
126
|
+
assert slot["satisfied"]
|
|
127
|
+
if not slot["satisfied"]:
|
|
128
|
+
assert slot["slot"] in resolved.structuredContent["unsatisfied_required"]
|
|
100
129
|
|
|
101
130
|
anyio.run(scenario)
|