xrefkit 0.7.1__tar.gz → 0.7.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. {xrefkit-0.7.1/xrefkit.egg-info → xrefkit-0.7.3}/PKG-INFO +1 -1
  2. {xrefkit-0.7.1 → xrefkit-0.7.3}/pyproject.toml +1 -1
  3. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_ci_workflow_invariants.py +67 -0
  4. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_cli.py +7 -1
  5. xrefkit-0.7.3/tests/test_compact_skill_logs.py +190 -0
  6. xrefkit-0.7.3/tests/test_contribution_adoption_bytes.py +240 -0
  7. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_contribution_returns.py +148 -0
  8. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skill_runtime_audit.py +435 -0
  9. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/__init__.py +1 -1
  10. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/catalog.py +8 -0
  11. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/contribution_returns.py +12 -8
  12. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/operations_cli.py +13 -0
  13. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/skillrun.py +388 -33
  14. {xrefkit-0.7.1 → xrefkit-0.7.3/xrefkit.egg-info}/PKG-INFO +1 -1
  15. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit.egg-info/SOURCES.txt +2 -0
  16. {xrefkit-0.7.1 → xrefkit-0.7.3}/LICENSE +0 -0
  17. {xrefkit-0.7.1 → xrefkit-0.7.3}/README.md +0 -0
  18. {xrefkit-0.7.1 → xrefkit-0.7.3}/setup.cfg +0 -0
  19. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_artifact_selected_family.py +0 -0
  20. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_attention_pet.py +0 -0
  21. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_attention_pet_client_protocol.py +0 -0
  22. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_attention_pet_codex_session.py +0 -0
  23. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_attention_pet_fit.py +0 -0
  24. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_azure_connection.py +0 -0
  25. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_azure_import.py +0 -0
  26. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_azure_recovery.py +0 -0
  27. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_azure_update_candidate.py +0 -0
  28. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_azure_writer.py +0 -0
  29. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_boundary_analysis.py +0 -0
  30. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_brownfield_csharp_structure_definitions.py +0 -0
  31. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_brownfield_file_editing_protocol.py +0 -0
  32. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_business_intake_conversation_definitions.py +0 -0
  33. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_business_intake_definition.py +0 -0
  34. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_calibration_lint.py +0 -0
  35. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_catalog_preparation_definition.py +0 -0
  36. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_check_skill_knowledge_xids.py +0 -0
  37. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_code_constraint_definition.py +0 -0
  38. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_collect_analyzer_sarif.py +0 -0
  39. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_constraint_derivation_family_definition.py +0 -0
  40. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_constraint_derivation_selected_family.py +0 -0
  41. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_convert_to_xrefkit_skill.py +0 -0
  42. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_cs_scope_probe.py +0 -0
  43. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_csharp_commonality.py +0 -0
  44. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_csharp_naming_profile.py +0 -0
  45. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_ctx.py +0 -0
  46. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_cutover_readiness.py +0 -0
  47. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_dashboard.py +0 -0
  48. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_database_batch_definition_migration.py +0 -0
  49. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_decision_trace.py +0 -0
  50. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_design_business_intake_selected_family.py +0 -0
  51. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_design_planning_definition_migration.py +0 -0
  52. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_dotnet_definition_candidate.py +0 -0
  53. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_editorial_family_definition.py +0 -0
  54. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_editorial_intake_definition.py +0 -0
  55. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_editorial_selected_family.py +0 -0
  56. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_embedded_startup_pack.py +0 -0
  57. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_error_policy_audit.py +0 -0
  58. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_error_policy_locator.py +0 -0
  59. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_execution_binding.py +0 -0
  60. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_existing_skill_definition_batch.py +0 -0
  61. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_final_skilldefinition_batch.py +0 -0
  62. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_flow_selected_family.py +0 -0
  63. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_fm_multiroot.py +0 -0
  64. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_gate.py +0 -0
  65. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_gateway.py +0 -0
  66. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_gateway_mcp.py +0 -0
  67. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_gateway_skill_adapter.py +0 -0
  68. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_gateway_work_items.py +0 -0
  69. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_goal_desired_state.py +0 -0
  70. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_governance_entry.py +0 -0
  71. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_host_precheck.py +0 -0
  72. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_human_evaluation.py +0 -0
  73. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_implementation_flow_definition.py +0 -0
  74. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_implementation_review_selected_family.py +0 -0
  75. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_inbound_uploads.py +0 -0
  76. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_instruction_workflow.py +0 -0
  77. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_knowledge_relations_validator.py +0 -0
  78. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_language_review_definition.py +0 -0
  79. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_legacy_guidance.py +0 -0
  80. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_legacy_migration.py +0 -0
  81. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_legacy_resources.py +0 -0
  82. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_mcp_package_routing.py +0 -0
  83. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_mcp_setup.py +0 -0
  84. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_mcp_subagent_startup.py +0 -0
  85. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_mcp_subagent_startup_integration.py +0 -0
  86. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_ontology_assessment_validator.py +0 -0
  87. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_os_family_definition.py +0 -0
  88. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_os_knowledge_family_definition.py +0 -0
  89. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_os_selected_family.py +0 -0
  90. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_ownership.py +0 -0
  91. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_packmeta.py +0 -0
  92. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_plan_markdown.py +0 -0
  93. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_plan_observation.py +0 -0
  94. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_project_quality_baseline.py +0 -0
  95. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_qa_report_definition.py +0 -0
  96. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_reporting_profiles.py +0 -0
  97. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_reporting_quality.py +0 -0
  98. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_repository_skill_adoption.py +0 -0
  99. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_repository_startup_pack.py +0 -0
  100. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_resource_provider.py +0 -0
  101. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_runtime_contracts.py +0 -0
  102. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_sarif_to_locator.py +0 -0
  103. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_security_definition_migration.py +0 -0
  104. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_site_release_metadata.py +0 -0
  105. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skill_definition.py +0 -0
  106. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skill_definition_governance.py +0 -0
  107. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skill_definition_mcp.py +0 -0
  108. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skill_definition_runtime.py +0 -0
  109. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skill_edits.py +0 -0
  110. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skill_maturity_return.py +0 -0
  111. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skill_release_compatibility.py +0 -0
  112. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skillmeta.py +0 -0
  113. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_skills_sync.py +0 -0
  114. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_source_analysis_fragments.py +0 -0
  115. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_structure_catalog.py +0 -0
  116. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_subagent_startup.py +0 -0
  117. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_subagent_startup_boundaries.py +0 -0
  118. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_wbs.py +0 -0
  119. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_work_management.py +0 -0
  120. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_work_packages.py +0 -0
  121. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_workflow_family_definition.py +0 -0
  122. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_xref.py +0 -0
  123. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_xrefkit_instance.py +0 -0
  124. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_xrefkit_tools.py +0 -0
  125. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_xrefkit_v2_discovery.py +0 -0
  126. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_xrefkit_v2_models.py +0 -0
  127. {xrefkit-0.7.1 → xrefkit-0.7.3}/tests/test_xrefkit_v2_pipeline.py +0 -0
  128. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/__main__.py +0 -0
  129. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/__init__.py +0 -0
  130. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/__main__.py +0 -0
  131. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/client.py +0 -0
  132. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/client_protocol.py +0 -0
  133. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/codex_session.py +0 -0
  134. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/evaluator.py +0 -0
  135. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/model.py +0 -0
  136. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/presentation.py +0 -0
  137. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/profiles.py +0 -0
  138. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/scenarios.py +0 -0
  139. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/server.py +0 -0
  140. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/attention_pet/store.py +0 -0
  141. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/azure_connection.py +0 -0
  142. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/azure_import.py +0 -0
  143. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/azure_recovery.py +0 -0
  144. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/azure_update_candidate.py +0 -0
  145. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/azure_writer.py +0 -0
  146. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/boundary_analysis.py +0 -0
  147. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/catalog_cli.py +0 -0
  148. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/cli.py +0 -0
  149. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/contracts.py +0 -0
  150. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/ctx.py +0 -0
  151. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/dashboard.py +0 -0
  152. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/decision_trace.py +0 -0
  153. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/discovery.py +0 -0
  154. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/execution_binding.py +0 -0
  155. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/execution_binding_cli.py +0 -0
  156. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/gate.py +0 -0
  157. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/gateway.py +0 -0
  158. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/goalstate.py +0 -0
  159. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/governance_entry.py +0 -0
  160. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/governance_entry_cli.py +0 -0
  161. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/hashing.py +0 -0
  162. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/host_precheck.py +0 -0
  163. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/import_skill.py +0 -0
  164. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/instance.py +0 -0
  165. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/loaders.py +0 -0
  166. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/__init__.py +0 -0
  167. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/audit.py +0 -0
  168. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/bootstrap.py +0 -0
  169. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/cli.py +0 -0
  170. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/client_cache.py +0 -0
  171. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/client_flow.py +0 -0
  172. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/context_registry.py +0 -0
  173. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/context_token.py +0 -0
  174. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/contracts.py +0 -0
  175. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/contribution_adoption.py +0 -0
  176. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/dist.py +0 -0
  177. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/gateway.py +0 -0
  178. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/governed_overlays.py +0 -0
  179. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/inbound_uploads.py +0 -0
  180. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/knowledge_edits.py +0 -0
  181. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/legacy_guidance.py +0 -0
  182. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/legacy_migration.py +0 -0
  183. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/legacy_resources.py +0 -0
  184. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/ownership.py +0 -0
  185. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/repository.py +0 -0
  186. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/schemas.py +0 -0
  187. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/server.py +0 -0
  188. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/setup.py +0 -0
  189. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/skill_edits.py +0 -0
  190. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/skill_maturity.py +0 -0
  191. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/startup_contract_pack.py +0 -0
  192. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/subagent_startup.py +0 -0
  193. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp/update_gate_intake.py +0 -0
  194. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/mcp_tools.py +0 -0
  195. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/__init__.py +0 -0
  196. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/common.py +0 -0
  197. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/effective_bundle.py +0 -0
  198. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/human_evaluation.py +0 -0
  199. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/local_manifest.py +0 -0
  200. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/package_manifest.py +0 -0
  201. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/run_log.py +0 -0
  202. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/server_config.py +0 -0
  203. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/models/skill_definition.py +0 -0
  204. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/ownership.py +0 -0
  205. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/packmeta.py +0 -0
  206. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/plan_markdown.py +0 -0
  207. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/plan_observation.py +0 -0
  208. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/registry.py +0 -0
  209. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/reporting.py +0 -0
  210. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/reporting_profiles.py +0 -0
  211. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/repository_skills.py +0 -0
  212. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resolver.py +0 -0
  213. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resource_provider.py +0 -0
  214. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/attention_pet/client-state.schema.json +0 -0
  215. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/attention_pet/pet.css +0 -0
  216. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/attention_pet/pet.html +0 -0
  217. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/attention_pet/pet.js +0 -0
  218. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/contracts.json +0 -0
  219. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/current.json +0 -0
  220. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
  221. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
  222. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
  223. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
  224. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/model_body.md +0 -0
  225. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/startup_sources/000_agent_entry.md +0 -0
  226. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/startup_sources/011_startup_xref_routing.md +0 -0
  227. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/startup_sources/015_shared_memory_operations.md +0 -0
  228. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/startup_sources/016_uncertainty_protocol.md +0 -0
  229. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/startup_sources/017_base_and_xref_layering.md +0 -0
  230. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/startup_sources/053_context_direction_security_guard.md +0 -0
  231. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/base/startup_sources/yomiyasu_LICENSE.txt +0 -0
  232. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/migration/docs/guides/062_legacy_flow_skill_migration_guide.md +0 -0
  233. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/migration/knowledge/operations/130_legacy_flow_skill_migration_rules.md +0 -0
  234. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/migration/knowledge/organization/160_context_direction_guard_rules.md +0 -0
  235. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/migration/skills/os/legacy_flow_skill_migration/SKILL.v1.md +0 -0
  236. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/migration/skills/os/legacy_flow_skill_migration/references/legacy_flow_skill_migration_template.md +0 -0
  237. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/migration/skills/repository_adoption.json +0 -0
  238. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/resources/migration/tools/migrate_legacy_flow_skill.py +0 -0
  239. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/runlog.py +0 -0
  240. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/skill_definition.py +0 -0
  241. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/skill_definition_catalog.py +0 -0
  242. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/skill_definition_governance.py +0 -0
  243. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/skillmeta.py +0 -0
  244. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/skills_sync.py +0 -0
  245. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/structure_catalog.py +0 -0
  246. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/subagent_startup.py +0 -0
  247. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/tools/__init__.py +0 -0
  248. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/tools/__main__.py +0 -0
  249. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/v2_cli.py +0 -0
  250. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/wbs.py +0 -0
  251. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/work_management.py +0 -0
  252. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/work_management_view.py +0 -0
  253. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/workspace.py +0 -0
  254. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit/xref.py +0 -0
  255. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit.egg-info/dependency_links.txt +0 -0
  256. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit.egg-info/entry_points.txt +0 -0
  257. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit.egg-info/requires.txt +0 -0
  258. {xrefkit-0.7.1 → xrefkit-0.7.3}/xrefkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xrefkit
3
- Version: 0.7.1
3
+ Version: 0.7.3
4
4
  Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
5
5
  Author: synthaicode
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xrefkit"
7
- version = "0.7.1"
7
+ version = "0.7.3"
8
8
  description = "Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,5 +1,7 @@
1
1
  """CI cleanup must retain release coverage and only cancel obsolete PR runs."""
2
2
  from pathlib import Path
3
+ import shutil
4
+ import subprocess
3
5
 
4
6
  import pytest
5
7
  import yaml
@@ -16,6 +18,36 @@ PACKAGES = {
16
18
  }
17
19
 
18
20
 
21
+ @pytest.mark.parametrize("stop_status,remains,expected", [
22
+ (0, False, 0), (1, False, 0), (1, True, 1), (2, False, 2),
23
+ ])
24
+ def test_core_updater_stop_handles_exit_race_without_hiding_errors(stop_status, remains, expected):
25
+ bash = shutil.which("bash")
26
+ if Path("C:/Program Files/Git/bin/bash.exe").is_file():
27
+ bash = "C:/Program Files/Git/bin/bash.exe"
28
+ if not bash:
29
+ pytest.skip("Bash is required to execute the workflow stop block")
30
+ steps = workflow("python-package.yml")["jobs"]["artifact-inspection"]["steps"]
31
+ update = next(s["run"] for s in steps if "sudo apt-get install --yes clamav" in s.get("run", ""))
32
+ block = update.split("# Package installation", 1)[1].split("# A failed definition update", 1)[0]
33
+ block = "# Package installation" + block
34
+ mocks = f"""
35
+ set -euo pipefail
36
+ calls=0
37
+ pgrep() {{
38
+ calls=$((calls + 1))
39
+ if [ "$calls" -eq 1 ]; then return 0; fi
40
+ return {0 if remains else 1}
41
+ }}
42
+ sudo() {{ return {stop_status}; }}
43
+ sleep() {{ :; }}
44
+ """
45
+ result = subprocess.run([bash, "-c", mocks + block + "\necho stop-complete\n"],
46
+ capture_output=True, text=True)
47
+ assert result.returncode == expected, result.stdout + result.stderr
48
+ assert ("stop-complete" in result.stdout) == (expected == 0)
49
+
50
+
19
51
  def workflow(filename):
20
52
  # BaseLoader preserves GitHub's `on` key instead of YAML 1.1's boolean True.
21
53
  return yaml.load((WORKFLOWS / filename).read_text(encoding="utf-8"), Loader=yaml.BaseLoader)
@@ -188,3 +220,38 @@ def test_shared_tools_trigger_only_consumers():
188
220
  assert not accepts_changes(filename, "pull_request", ["tools/run_quality_gate.py"])
189
221
  assert not accepts_changes("python-skill-csharp-package.yml", "pull_request", ["tools/run_import_smoke.py"])
190
222
  assert not accepts_changes("xrefkit-skills-xddp-design.yml", "pull_request", ["xrefkit/cli.py"])
223
+
224
+
225
+ def test_pages_deployment_is_main_only_and_release_dispatch_has_no_environment():
226
+ data = workflow("pages.yml")
227
+ deploy = data["jobs"]["deploy"]
228
+ condition = " ".join(deploy["if"].split())
229
+ assert condition.startswith("github.ref == 'refs/heads/main' && (")
230
+ assert deploy["environment"]["name"] == "github-pages"
231
+ route = data["jobs"]["dispatch-release"]
232
+ assert route["if"] == "github.event_name == 'release'"
233
+ assert "environment" not in route
234
+ assert route["permissions"] == {"contents": "read", "actions": "write"}
235
+ assert data["permissions"]["pages"] == "write"
236
+ step = route["steps"][0]
237
+ assert step["env"]["RELEASE_REPOSITORY"] == "${{ github.repository }}"
238
+ assert step["env"]["GH_TOKEN"] == "${{ github.token }}"
239
+ assert "github.event.release" not in step["run"]
240
+
241
+
242
+ @pytest.mark.parametrize("dispatch_exit", [0, 37])
243
+ def test_pages_release_dispatch_executes_fixed_main_target_and_preserves_failure(dispatch_exit):
244
+ bash = shutil.which("bash")
245
+ if Path("C:/Program Files/Git/bin/bash.exe").is_file():
246
+ bash = "C:/Program Files/Git/bin/bash.exe"
247
+ if not bash:
248
+ pytest.skip("Bash is required to execute the Pages dispatch block")
249
+ script = workflow("pages.yml")["jobs"]["dispatch-release"]["steps"][0]["run"]
250
+ mock = ('set -euo pipefail\nRELEASE_REPOSITORY=synthaicode/XRefKit\n'
251
+ 'gh() { printf "<%s>\\n" "$@"; return ' + str(dispatch_exit) + '; }\n')
252
+ result = subprocess.run([bash, "-c", mock + script + "\necho dispatch-complete\n"],
253
+ capture_output=True, text=True)
254
+ assert result.returncode == dispatch_exit, result.stdout + result.stderr
255
+ assert result.stdout.splitlines()[:6] == ["<api>", "<--method>", "<POST>",
256
+ "<repos/synthaicode/XRefKit/actions/workflows/pages.yml/dispatches>", "<-f>", "<ref=main>"]
257
+ assert ("dispatch-complete" in result.stdout) == (dispatch_exit == 0)
@@ -1000,7 +1000,13 @@ class CliTests(unittest.TestCase):
1000
1000
  )
1001
1001
  text = out.read_text(encoding="utf-8")
1002
1002
  self.assertIn("## Domain Knowledge Inputs", text)
1003
- self.assertIn("- rule: available and selected brownfield domain knowledge is recorded by XID only", text)
1003
+ self.assertIn("- log_format: `compact_v1`", text)
1004
+ self.assertIn("- operating_contract_xid: `B7A2C94F0E61`", text)
1005
+ self.assertIn(
1006
+ "### Selected Knowledge Inputs\n\n- target_service_structure:\n"
1007
+ " - `LOCAL-SERVICE-MAP-001`\n - `LOCAL-DB-SCHEMA-001`",
1008
+ text,
1009
+ )
1004
1010
  self.assertIn("- xid: `LOCAL-SERVICE-MAP-001`", text)
1005
1011
  self.assertIn("content_hash: `sha256-service-map`", text)
1006
1012
  self.assertIn("last_verified: `2026-07-04`", text)
@@ -0,0 +1,190 @@
1
+ import pytest
2
+
3
+ from test_skill_definition_runtime import _open_run
4
+ from test_subagent_startup import command
5
+ from xrefkit.skillrun import _section_body
6
+
7
+
8
+ @pytest.mark.parametrize("legacy", [False, True])
9
+ def test_compact_and_legacy_logs_keep_evidence_through_closure(tmp_path, legacy):
10
+ _, log = _open_run(tmp_path)
11
+ text = log.read_text(encoding="utf-8")
12
+ marker = "- log_format: `compact_v1`\n- operating_contract_xid: `B7A2C94F0E61`\n"
13
+ assert marker in text
14
+ assert "- rule:" not in text
15
+ if legacy:
16
+ text = text.replace(marker, "", 1)
17
+ text = text.replace(
18
+ "## Token Usage\n\n", "## Token Usage\n\n- rule: legacy token guidance\n", 1
19
+ )
20
+ log.write_text(text, encoding="utf-8")
21
+
22
+ def update(*args):
23
+ code, output = command("skill", *args, "--log", str(log))
24
+ assert code == 0, output
25
+
26
+ # User data that resembles omitted template lines must survive updates.
27
+ note = "literal evidence\n- rule: user supplied detail\n- log_format: `compact_v1`"
28
+ update("tokens", "--input", "120", "--output", "30", "--note", note)
29
+ update("workitem", "--item", "WI-1", "--completion-criterion", "report covers target", "--status", "done", "--role", "definition_sample:executor")
30
+ for artifact_id, kind, target in (
31
+ ("OUT", "output", "report.md"), ("EVIDENCE", "evidence", "tests passed"),
32
+ ):
33
+ update("artifact", "--artifact", artifact_id, "--kind", kind, "--target", target,
34
+ "--item", "WI-1", "--status", "done", "--role", "definition_sample:executor")
35
+ update("concern", "--concern", "RISK", "--kind", "risk", "--status", "resolved",
36
+ "--text", "Compatibility checked", "--target", "tests passed", "--role", "definition_sample:executor")
37
+ for phase in ("startup", "planning", "execution"):
38
+ update("phase", "--phase", phase, "--status", "done", "--role", "definition_sample:executor")
39
+ update("verify")
40
+ update("phase", "--phase", "handoff", "--status", "done", "--role", "definition_sample:handoff_owner")
41
+ update("close")
42
+ text = log.read_text(encoding="utf-8")
43
+ assert note in text
44
+ assert "Compatibility checked" in text
45
+ assert ("- rule: each work item requires" in text) is legacy
46
+ assert ("- rule: outputs, evidence" in text) is legacy
47
+ assert ("- rule: unknowns, missing evidence" in text) is legacy
48
+ assert "- total: `150`" in text
49
+ assert "- target: report.md" in text or "target=`report.md`" in text
50
+ assert "## Closure Gate\n\n- status: `done`" in text
51
+ assert ("- rule: record tokens consumed" in text) is legacy
52
+ for section in ("Worklist", "Quality Gate"):
53
+ body, _, _ = _section_body(text, section)
54
+ for heading in ("Status", "Result", "Checks Performed", "Evidence", "Open Items", "Handoff"):
55
+ assert f"### {heading}\n" in body
56
+
57
+
58
+ def test_workflow_log_keeps_existing_format(tmp_path):
59
+ log = tmp_path / "work" / "workflow.md"
60
+ code, output = command(
61
+ "workflow", "run", "--root", str(tmp_path), "--task", "Inspect target",
62
+ "--completion-condition", "Target inspected", "--out", str(log),
63
+ )
64
+ assert code == 0, output
65
+ original = log.read_text(encoding="utf-8")
66
+ assert "log_format" not in original
67
+ assert "- rule: do not treat the instruction" in original
68
+ code, output = command("skill", "tokens", "--log", str(log), "--total", "20")
69
+ assert code == 0, output
70
+ assert "- rule: record tokens consumed" in log.read_text(encoding="utf-8")
71
+
72
+
73
+ def test_compaction_only_removes_template_rules_and_preserves_task(monkeypatch, tmp_path):
74
+ from xrefkit import skillrun
75
+
76
+ render = skillrun._render_log
77
+ captured = {}
78
+
79
+ def capture(**kwargs):
80
+ captured.update(kwargs)
81
+ return render(**kwargs)
82
+
83
+ monkeypatch.setattr(skillrun, "_render_log", capture)
84
+ _open_run(tmp_path)
85
+ captured["task"] = "Keep these literal lines:\n- rule: task requirement\n- log_format: `other`"
86
+ compact = render(**captured)
87
+ verbose = render(**{**captured, "compact": False})
88
+ assert captured["task"] in compact
89
+ assert len(compact.encode("utf-8")) < len(verbose.encode("utf-8")) * 0.8
90
+
91
+ # Every non-boilerplate line survives in the same order, including reports.
92
+ def data_lines(text):
93
+ return [line for line in text.splitlines() if line and not line.startswith((
94
+ "- rule:", "- log_format:", "- operating_contract_xid:",
95
+ ))]
96
+
97
+ assert data_lines(compact) == data_lines(verbose)
98
+
99
+
100
+ def test_dashboard_payload_is_identical_for_compact_and_verbose_runs(monkeypatch, tmp_path):
101
+ import json
102
+ import os
103
+ from datetime import datetime, timezone
104
+ from itertools import count
105
+ from uuid import UUID
106
+
107
+ from xrefkit import dashboard, skillrun
108
+
109
+ render = skillrun._render_log
110
+ captured = {}
111
+
112
+ def capture(**kwargs):
113
+ captured.update(kwargs)
114
+ return render(**kwargs)
115
+
116
+ monkeypatch.setattr(skillrun, "_render_log", capture)
117
+ _, log = _open_run(tmp_path)
118
+ provenance = {"capability": {"origin": "explicit_input", "selected_value": "repository inspection"}}
119
+
120
+ class FixedDatetime(datetime):
121
+ @classmethod
122
+ def now(cls, tz=None):
123
+ fixed = cls(2026, 10, 25, 12, 0, tzinfo=timezone.utc)
124
+ return fixed.astimezone(tz) if tz else fixed.replace(tzinfo=None)
125
+
126
+ monkeypatch.setattr(skillrun, "datetime", FixedDatetime)
127
+ monkeypatch.setattr(dashboard, "datetime", FixedDatetime)
128
+ snapshots = []
129
+ for compact in (False, True):
130
+ event_ids = count(1)
131
+ monkeypatch.setattr(skillrun.uuid, "uuid4", lambda: UUID(int=next(event_ids)))
132
+ identity = {**captured["definition_identity"], "runtime_provenance": json.dumps(
133
+ provenance, sort_keys=True, separators=(",", ":") if compact else None,
134
+ )}
135
+ log.write_text(render(**{**captured, "definition_identity": identity, "compact": compact}), encoding="utf-8")
136
+
137
+ def snapshot():
138
+ # Compare the whole public payload, with no fields removed. The same
139
+ # file path and frozen clocks make file/observation timestamps equal.
140
+ os.utime(log, (1792929600, 1792929600))
141
+ return dashboard.build_payload(tmp_path, log.parent)
142
+
143
+ def update(*args):
144
+ code, output = command("skill", *args, "--log", str(log))
145
+ assert code == 0, output
146
+
147
+ initial = snapshot()
148
+ assert "token_usage" in {item["code"] for item in initial["runs"][0]["missing_information"]}
149
+ update("workitem", "--item", "WI-1", "--text", "Inspect target",
150
+ "--completion-criterion", "report covers target", "--status", "done", "--role", "definition_sample:executor")
151
+ for artifact_id, kind, target in (("OUT-1", "output", "report.md"), ("EVD-1", "evidence", "tests passed")):
152
+ update("artifact", "--artifact", artifact_id, "--kind", kind, "--target", target,
153
+ "--item", "WI-1", "--status", "done", "--role", "definition_sample:executor")
154
+ update("concern", "--concern", "RISK-1", "--kind", "risk", "--status", "resolved",
155
+ "--text", "Compatibility checked", "--target", "tests passed", "--role", "definition_sample:executor")
156
+ update("correlate", "--run-id", captured["run_id"], "--mcp-session-id", "mcp-1", "--repository-fingerprint", "repo-1")
157
+ update("routing", "--selected-skill", "definition_sample", "--candidate", "definition_sample", "--reason", "best match")
158
+ update("knowledge", "--action", "search", "--query", "service map", "--status", "hit", "--xid", "SERVICE123456")
159
+ update("knowledge", "--action", "load", "--xid", "SERVICE123456", "--content-hash", "hash-1")
160
+ update("knowledge", "--action", "apply", "--xid", "SERVICE123456", "--content-hash", "hash-1", "--target", "OUT-1")
161
+ update("feedback", "--kind", "human", "--status", "accepted", "--note", "accepted")
162
+ update("feedback", "--kind", "outcome", "--status", "successful", "--note", "successful")
163
+ update("tokens", "--input", "120", "--output", "30")
164
+ populated = snapshot()
165
+ run = populated["runs"][0]
166
+ assert run["missing_information"] == []
167
+ assert run["counts"]["work_items"] == 1
168
+ assert run["counts"]["artifacts"] == 2
169
+ assert run["counts"]["risks"] == 1
170
+ assert run["loaded_xids"] == run["used_xids"] == ["SERVICE123456"]
171
+ assert run["definition_format"] == "skill_definition_v1"
172
+ assert run["definition_xid"] == "ABCDEF123456"
173
+ assert run["definition_sha256"] == captured["definition_identity"]["sha256"]
174
+ assert run["capability"] == "repository inspection"
175
+ assert {event["event"] for event in run["observation_events"]} >= {
176
+ "skill.routed", "knowledge.search", "knowledge.loaded", "knowledge.applied", "human.feedback", "outcome.feedback",
177
+ }
178
+ for phase in ("startup", "planning", "execution"):
179
+ update("phase", "--phase", phase, "--status", "done", "--role", "definition_sample:executor")
180
+ update("verify")
181
+ update("phase", "--phase", "handoff", "--status", "done", "--role", "definition_sample:handoff_owner")
182
+ update("close")
183
+ closed = snapshot()
184
+ assert closed["runs"][0]["status"] == "closed"
185
+ text = log.read_text(encoding="utf-8")
186
+ assert json.loads(skillrun._log_field(text, "definition_runtime_provenance")) == provenance
187
+ assert skillrun._parse_token_usage(text) == {"status": "recorded", "input": "120", "output": "30", "total": "150"}
188
+ snapshots.append((initial, populated, closed))
189
+
190
+ assert snapshots[0] == snapshots[1]
@@ -0,0 +1,240 @@
1
+ """Raw-byte preparation checks without human review, gate dispatch or adoption."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ import uuid
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ from test_subagent_startup import command
12
+ from xrefkit import governance_entry
13
+ from xrefkit.mcp import contribution_returns as cr
14
+ from xrefkit.mcp.audit import SessionRunBinding
15
+ from xrefkit.mcp.catalog import XRefCatalog
16
+ from xrefkit.mcp.update_gate_intake import prepare_contribution_gate
17
+ from xrefkit.ownership import Ownership, Zone
18
+
19
+
20
+ VARIANTS = [
21
+ pytest.param(b"# Rule\nalpha\nbeta\n", id="LF"),
22
+ pytest.param(b"# Rule\r\nalpha\r\nbeta\r\n", id="CRLF"),
23
+ pytest.param(b"# Rule\ralpha\rbeta\r", id="CR"),
24
+ pytest.param(b"# Rule\r\nalpha\nbeta\rgamma\r\n", id="mixed"),
25
+ pytest.param("# 検証\n条件:日本語\n".encode(), id="nonASCII-LF"),
26
+ pytest.param("# 検証\r\n条件:日本語\r\n".encode(), id="nonASCII-CRLF"),
27
+ ]
28
+
29
+
30
+ def _pending(root: Path, raw: bytes) -> tuple[Path, dict]:
31
+ content = "<!-- xid: BUG3TEST01 -->\n" + raw.decode("utf-8", errors="strict")
32
+ identity = str(uuid.uuid4())
33
+ cr.submit_contribution_return(
34
+ root,
35
+ binding=SessionRunBinding(str(uuid.uuid4()), str(uuid.uuid4()), "a" * 32, "sample"),
36
+ source_snapshot={"provider_id": "xrefkit-mcp", "provider_version": "test",
37
+ "package_id": None, "package_version": None, "skill_id": "sample",
38
+ "skill_content_hash": "b" * 64, "knowledge_versions": [],
39
+ "verification": "matched_current_catalog"},
40
+ contribution_id=identity, kind="knowledge", title="Byte fixture",
41
+ summary="Unapproved preparation fixture", files=[{"path": "rule.md", "content": content,
42
+ "content_hash": hashlib.sha256(content.encode()).hexdigest()}],
43
+ knowledge={"xid": "BUG3TEST01"}, proposed_target_path="knowledge/byte_fixture.md",
44
+ )
45
+ record = root / cr.STORE_RELATIVE / identity
46
+ return record, json.loads((record / "manifest.json").read_bytes())
47
+
48
+
49
+ def _inventory(root: Path) -> dict[str, bytes]:
50
+ return {p.relative_to(root).as_posix(): p.read_bytes()
51
+ for p in root.rglob("*") if p.is_file()}
52
+
53
+
54
+ @pytest.mark.parametrize("raw", VARIANTS)
55
+ def test_stored_files_preserve_original_bytes_and_repeated_reads(tmp_path: Path, raw: bytes) -> None:
56
+ record, manifest = _pending(tmp_path, raw)
57
+ original = (record / "files/rule.md").read_bytes()
58
+ before = _inventory(tmp_path)
59
+ assert cr.list_contribution_returns(tmp_path)[0]["status"] == "pending_review"
60
+ exported = cr.export_contribution_return(tmp_path, manifest["contribution_id"])
61
+ assert exported["review_bundle"]["files"][0]["content"].encode() == original
62
+ cr._verify_manifest_payload(record, manifest)
63
+ first = cr._stored_files(record, manifest)
64
+ assert first == cr._stored_files(record, manifest)
65
+ assert first[0].content.encode("utf-8") == original
66
+ assert first[0].content_hash == hashlib.sha256(original).hexdigest()
67
+ assert manifest["files"][0]["byte_count"] == len(original)
68
+ # This is the same encode used by gate intake, without bypassing its approval gate.
69
+ staged = tmp_path / "scratch/unapproved-candidate.md"
70
+ staged.parent.mkdir()
71
+ staged.write_bytes(first[0].content.encode("utf-8"))
72
+ assert staged.read_bytes() == original
73
+ staged.unlink()
74
+ assert _inventory(tmp_path) == before
75
+ assert not (record / "events").exists()
76
+ assert not (tmp_path / "knowledge").exists()
77
+
78
+
79
+ @pytest.mark.parametrize("corruption", ["hash", "byte_count", "invalid_utf8", "changed_bytes", "missing", "newline_only"])
80
+ def test_stored_files_reject_corrupt_candidate(tmp_path: Path, corruption: str) -> None:
81
+ record, manifest = _pending(tmp_path, b"# Rule\nbody\n")
82
+ stored = record / "files/rule.md"
83
+ if corruption == "hash":
84
+ manifest["files"][0]["content_hash"] = "0" * 64
85
+ elif corruption == "byte_count":
86
+ manifest["files"][0]["byte_count"] += 1
87
+ elif corruption == "invalid_utf8":
88
+ invalid = stored.read_bytes() + b"\xff"
89
+ stored.write_bytes(invalid)
90
+ manifest["files"][0].update(content_hash=hashlib.sha256(invalid).hexdigest(), byte_count=len(invalid))
91
+ elif corruption == "changed_bytes":
92
+ stored.write_bytes(stored.read_bytes() + b"tamper")
93
+ elif corruption == "newline_only":
94
+ stored.write_bytes(stored.read_bytes().replace(b"\n", b"\r\n"))
95
+ else:
96
+ stored.unlink()
97
+ before = _inventory(tmp_path)
98
+ with pytest.raises((ValueError, FileNotFoundError)):
99
+ cr._stored_files(record, manifest)
100
+ assert _inventory(tmp_path) == before
101
+
102
+
103
+ @pytest.mark.parametrize("corruption", ["payload", "extra_file", "deleted_file", "byte_count", "metadata_xid_tamper"])
104
+ def test_preparation_verifier_retains_manifest_and_tree_rejections(tmp_path: Path, corruption: str) -> None:
105
+ record, manifest = _pending(tmp_path, b"# Rule\nbody\n")
106
+ if corruption == "payload":
107
+ manifest["summary"] += " tampered"
108
+ elif corruption == "extra_file":
109
+ (record / "files/extra.md").write_bytes(b"extra")
110
+ elif corruption == "deleted_file":
111
+ (record / "files/rule.md").unlink()
112
+ elif corruption == "byte_count":
113
+ manifest["files"][0]["byte_count"] += 1
114
+ else:
115
+ manifest["kind_metadata"]["xid"] = "UNLISTED01"
116
+ before = _inventory(tmp_path)
117
+ with pytest.raises(ValueError):
118
+ cr._verify_manifest_payload(record, manifest)
119
+ assert _inventory(tmp_path) == before
120
+
121
+
122
+ class _ReadOnlyTargetRoot:
123
+ """Route a logical target to a real scratch file; never create canonical paths."""
124
+ def __init__(self, target: Path):
125
+ self.target = target
126
+
127
+ def joinpath(self, *parts: str) -> Path:
128
+ assert parts == ("knowledge", "byte_fixture.md")
129
+ return self.target
130
+
131
+
132
+ def _target_check(target: Path, raw: bytes, **changes) -> str:
133
+ xid = "BUG3TEST01"
134
+ logical = "knowledge/byte_fixture.md"
135
+ kwargs = dict(ownership=Ownership((Zone("kernel-content", "base", ("knowledge/",), True,
136
+ True, True, False),)),
137
+ existing_knowledge_xids={xid}, existing_knowledge_xid_locations={xid: {logical}},
138
+ repository_xid_paths={xid: {logical}}, allow_same_xid_after_prepare=True)
139
+ kwargs.update(changes)
140
+ return cr._validate_canonical_target(
141
+ _ReadOnlyTargetRoot(target),
142
+ {"kind": "knowledge", "kind_metadata": {"xid": xid},
143
+ "files": [{"content_hash": hashlib.sha256(raw).hexdigest(), "byte_count": len(raw)}]},
144
+ logical, **kwargs,
145
+ )
146
+
147
+
148
+ @pytest.mark.parametrize("raw", VARIANTS)
149
+ def test_recovery_target_matches_raw_hash_without_newline_conversion(tmp_path: Path, raw: bytes) -> None:
150
+ target = tmp_path / "scratch-target.md"
151
+ target.write_bytes(raw)
152
+ before = target.read_bytes()
153
+ assert _target_check(target, raw) == "knowledge/byte_fixture.md"
154
+ assert target.read_bytes() == before
155
+ assert not (tmp_path / "knowledge").exists()
156
+
157
+
158
+ @pytest.mark.parametrize("mismatch", ["target_version", "expected_hash", "duplicate_xid", "unlocated_xid", "no_prepare", "missing_target", "newline_only"])
159
+ def test_recovery_retains_collision_and_version_rejections(tmp_path: Path, mismatch: str) -> None:
160
+ original = b"# Original\nbody\n"
161
+ target = tmp_path / "scratch-target.md"
162
+ target.write_bytes(original)
163
+ expected = original
164
+ changes = {}
165
+ if mismatch == "target_version":
166
+ target.write_bytes(b"# Other version\nbody\n")
167
+ elif mismatch == "newline_only":
168
+ target.write_bytes(original.replace(b"\n", b"\r\n"))
169
+ elif mismatch == "expected_hash":
170
+ expected = b"# Different expected base\nbody\n"
171
+ elif mismatch == "duplicate_xid":
172
+ changes["repository_xid_paths"] = {"BUG3TEST01": {"knowledge/byte_fixture.md", "knowledge/other.md"}}
173
+ elif mismatch == "unlocated_xid":
174
+ changes["unlocated_existing_knowledge_xids"] = {"BUG3TEST01"}
175
+ elif mismatch == "no_prepare":
176
+ changes["allow_same_xid_after_prepare"] = False
177
+ else:
178
+ target.unlink()
179
+ before = _inventory(tmp_path)
180
+ with pytest.raises(ValueError, match="Knowledge XID already exists"):
181
+ _target_check(target, expected, **changes)
182
+ assert _inventory(tmp_path) == before
183
+
184
+
185
+ def test_gate_preparation_requires_real_approval_and_leaves_pending_state(tmp_path: Path) -> None:
186
+ record, manifest = _pending(tmp_path, b"# Rule\r\nbody\r\n")
187
+ before = _inventory(tmp_path)
188
+ with pytest.raises(RuntimeError, match="trusted human approval verifier"):
189
+ prepare_contribution_gate(tmp_path, contribution_id=manifest["contribution_id"],
190
+ log="work/not-created.md", binding_request={},
191
+ specialist_evidence=[], approval_verifier=None)
192
+ assert _inventory(tmp_path) == before
193
+ assert not (record / "events").exists()
194
+ assert not (tmp_path / "knowledge").exists()
195
+
196
+
197
+ def test_unsigned_gate_packet_revalidation_rejects_wrong_base_hash(tmp_path: Path) -> None:
198
+ log = tmp_path / "work/run.md"
199
+ code, output = command("workflow", "run", "--root", str(tmp_path), "--task",
200
+ "Unsigned base hash regression fixture", "--out", str(log),
201
+ "--completion-condition", "read-only packet checked", "--json")
202
+ assert code == 0, output
203
+ code, output = command("skill", "workitem", "--log", str(log), "--item", "WI-BASE",
204
+ "--text", "Validate unsigned frozen packet", "--completion-criterion",
205
+ "base mismatch rejected without writing target", "--status", "pending",
206
+ "--role", "instruction:executor", "--json")
207
+ assert code == 0, output
208
+ paths = ["work/candidate.md", *sorted(governance_entry.RULE_SOURCES)]
209
+ for name in paths:
210
+ path = tmp_path / name
211
+ path.parent.mkdir(parents=True, exist_ok=True)
212
+ path.write_bytes(b"# Isolated unsigned packet material\r\n")
213
+ request = {"work_item_id": "WI-BASE", "source_mode": "filesystem", "purpose": "base hash test",
214
+ "capability": "analysis", "tuning": "read-only fixture", "responsibility": "reject changed base",
215
+ "instruction_basis": "Authorized BUG3 regression scope", "scope_in": paths,
216
+ "scope_out": ["approval", "dispatch", "adoption"], "stop_conditions": ["unexpected write"],
217
+ "protocols": ["workflow"], "knowledge_access": {"mode": "on_demand", "catalog": "work/index.md"}}
218
+ packet = governance_entry.prepare(
219
+ tmp_path, kind="asset_update", log="work/run.md", binding_request=request, materials=paths,
220
+ payload={"candidate": "work/candidate.md", "target": "knowledge/not-created.md",
221
+ "rules": sorted(governance_entry.RULE_SOURCES), "checks": sorted(governance_entry.CORE_CHECKS),
222
+ "specialist_evidence": []},
223
+ )
224
+ assert packet["payload"]["expected_target_hash"] is None
225
+ before = _inventory(tmp_path)
226
+ governance_entry.revalidate(tmp_path, packet)
227
+ packet["payload"]["expected_target_hash"] = "0" * 64
228
+ # Packet digest is an unsigned content digest, never human authority or a host receipt.
229
+ packet["packet_hash"] = governance_entry.digest({k: v for k, v in packet.items() if k != "packet_hash"})
230
+ with pytest.raises(ValueError, match="target baseline changed"):
231
+ governance_entry.revalidate(tmp_path, packet)
232
+ assert _inventory(tmp_path) == before
233
+ assert not (tmp_path / "knowledge").exists()
234
+
235
+
236
+ def test_nonexistent_xid_lookup_does_not_invent_a_document(tmp_path: Path) -> None:
237
+ before = _inventory(tmp_path)
238
+ with pytest.raises(KeyError, match="document xid not found: ABCDEF987654"):
239
+ XRefCatalog.build(tmp_path).get_document_by_xid("ABCDEF987654")
240
+ assert _inventory(tmp_path) == before