generic-ml-cache-core 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/PKG-INFO +1 -1
  2. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/pyproject.toml +1 -1
  3. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/persistence/in_memory_execution_repository.py +13 -1
  4. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/persistence/sqlite_execution_repository.py +47 -0
  5. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/execution/ml_execution.py +12 -1
  6. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/run_managed_local_execution_command.py +1 -0
  7. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/execution_repository_port.py +12 -0
  8. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/usecase/cached_ml_execution_service.py +15 -0
  9. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/usecase/run_managed_local_execution_service.py +5 -1
  10. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_execution_repository.py +25 -0
  11. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_ml_execution.py +12 -1
  12. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_run_managed_local_execution_service.py +21 -0
  13. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_sqlite_execution_repository.py +25 -0
  14. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/.gitignore +0 -0
  15. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/LICENSE +0 -0
  16. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/NOTICE +0 -0
  17. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/README.md +0 -0
  18. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/__init__.py +0 -0
  19. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/__init__.py +0 -0
  20. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/inbound/__init__.py +0 -0
  21. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/inbound/composition.py +0 -0
  22. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/__init__.py +0 -0
  23. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/api/__init__.py +0 -0
  24. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/api/stub_api_client_adapter.py +0 -0
  25. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/__init__.py +0 -0
  26. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/claude.py +0 -0
  27. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/codex.py +0 -0
  28. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/cursor.py +0 -0
  29. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/discover.py +0 -0
  30. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/isolation.py +0 -0
  31. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/local_client_runner.py +0 -0
  32. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/passthrough_client_runner.py +0 -0
  33. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/prime_directive.py +0 -0
  34. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/client/registry.py +0 -0
  35. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/clock/__init__.py +0 -0
  36. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/clock/system_clock.py +0 -0
  37. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/fingerprint/__init__.py +0 -0
  38. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/fingerprint/filesystem_file_fingerprint.py +0 -0
  39. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/metrics/__init__.py +0 -0
  40. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/metrics/access_registry.py +0 -0
  41. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/metrics/journal_metrics.py +0 -0
  42. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/persistence/__init__.py +0 -0
  43. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/persistence/call_identity_serialization.py +0 -0
  44. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/storage/__init__.py +0 -0
  45. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/adapter/out/storage/filesystem_blob_store.py +0 -0
  46. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/__init__.py +0 -0
  47. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/__init__.py +0 -0
  48. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/__init__.py +0 -0
  49. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/client_status.py +0 -0
  50. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/execution/__init__.py +0 -0
  51. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/execution/artifact.py +0 -0
  52. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_failure.py +0 -0
  53. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_kind.py +0 -0
  54. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_state.py +0 -0
  55. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/identity/__init__.py +0 -0
  56. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/identity/api_call_identity.py +0 -0
  57. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/identity/call_identity.py +0 -0
  58. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/identity/managed_call_identity.py +0 -0
  59. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/identity/passthrough_call_identity.py +0 -0
  60. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/model_info.py +0 -0
  61. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/model_listing.py +0 -0
  62. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/parsed_output.py +0 -0
  63. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/probe/__init__.py +0 -0
  64. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/probe/probe_report.py +0 -0
  65. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/probe/probe_status.py +0 -0
  66. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/run/__init__.py +0 -0
  67. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/run/cache_mode.py +0 -0
  68. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/run/client_run_request.py +0 -0
  69. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/run/client_run_result.py +0 -0
  70. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/run/message.py +0 -0
  71. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/usage/__init__.py +0 -0
  72. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/usage/token_usage.py +0 -0
  73. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/model/usage/usage.py +0 -0
  74. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/service/__init__.py +0 -0
  75. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/service/cacheability.py +0 -0
  76. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/domain/service/message_fingerprinting.py +0 -0
  77. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/__init__.py +0 -0
  78. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/__init__.py +0 -0
  79. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/probe_command.py +0 -0
  80. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/probe_use_case.py +0 -0
  81. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/run_api_execution_command.py +0 -0
  82. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/run_api_execution_use_case.py +0 -0
  83. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/run_managed_local_execution_use_case.py +0 -0
  84. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/run_passthrough_execution_command.py +0 -0
  85. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/inbound/run_passthrough_execution_use_case.py +0 -0
  86. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/__init__.py +0 -0
  87. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/api_client_port.py +0 -0
  88. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/base.py +0 -0
  89. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/blob_store_port.py +0 -0
  90. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/client_runner_port.py +0 -0
  91. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/clock_port.py +0 -0
  92. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/file_fingerprint_port.py +0 -0
  93. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/metrics_port.py +0 -0
  94. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/port/out/passthrough_runner_port.py +0 -0
  95. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/usecase/__init__.py +0 -0
  96. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/usecase/call_identity_building.py +0 -0
  97. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/usecase/journal_events.py +0 -0
  98. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/usecase/probe_service.py +0 -0
  99. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/usecase/run_api_execution_service.py +0 -0
  100. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/application/usecase/run_passthrough_execution_service.py +0 -0
  101. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/common/__init__.py +0 -0
  102. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/common/checksum.py +0 -0
  103. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/common/errors.py +0 -0
  104. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/src/generic_ml_cache_core/stream.py +0 -0
  105. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/conftest.py +0 -0
  106. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/fake_client.py +0 -0
  107. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_adapters.py +0 -0
  108. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_api_call_identity.py +0 -0
  109. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_api_client_port.py +0 -0
  110. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_artifact.py +0 -0
  111. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_blob_store_port.py +0 -0
  112. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_cache_mode.py +0 -0
  113. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_cacheability.py +0 -0
  114. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_call_identity_building.py +0 -0
  115. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_call_identity_serialization.py +0 -0
  116. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_checksum.py +0 -0
  117. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_client_run_request.py +0 -0
  118. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_client_run_result.py +0 -0
  119. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_client_runner_port.py +0 -0
  120. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_clock_port.py +0 -0
  121. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_composition.py +0 -0
  122. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_execution_failure.py +0 -0
  123. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_execution_kind.py +0 -0
  124. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_execution_state.py +0 -0
  125. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_file_content_fingerprint.py +0 -0
  126. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_file_fingerprint_port.py +0 -0
  127. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_filesystem_blob_store.py +0 -0
  128. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_journal_metrics.py +0 -0
  129. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_local_client_runner.py +0 -0
  130. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_managed_call_identity.py +0 -0
  131. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_message.py +0 -0
  132. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_message_fingerprinting.py +0 -0
  133. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_metrics_port.py +0 -0
  134. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_passthrough_call_identity.py +0 -0
  135. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_passthrough_client_runner.py +0 -0
  136. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_passthrough_runner_port.py +0 -0
  137. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_probe_command.py +0 -0
  138. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_probe_report.py +0 -0
  139. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_probe_service.py +0 -0
  140. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_run_api_execution_command.py +0 -0
  141. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_run_api_execution_service.py +0 -0
  142. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_run_managed_local_execution_command.py +0 -0
  143. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_run_passthrough_execution_command.py +0 -0
  144. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_run_passthrough_execution_service.py +0 -0
  145. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_token_usage.py +0 -0
  146. {generic_ml_cache_core-0.2.0 → generic_ml_cache_core-0.3.0}/tests/test_usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: generic-ml-cache-core
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Hexagonal core library for generic-ml-cache: domain, use cases, ports, and the default outbound adapters (SQLite repo, blob store, local clients, API). Stateless; inject the data source. Zero runtime deps.
5
5
  Project-URL: Homepage, https://github.com/danielslobozian/generic-ml-cache
6
6
  Project-URL: Repository, https://github.com/danielslobozian/generic-ml-cache
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "generic-ml-cache-core"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Hexagonal core library for generic-ml-cache: domain, use cases, ports, and the default outbound adapters (SQLite repo, blob store, local clients, API). Stateless; inject the data source. Zero runtime deps."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -5,7 +5,7 @@
5
5
  from __future__ import annotations
6
6
 
7
7
  from dataclasses import replace
8
- from typing import Dict, List, Optional
8
+ from typing import Dict, List, Optional, Set
9
9
 
10
10
  from generic_ml_cache_core.application.domain.model.execution.execution_state import ExecutionState
11
11
  from generic_ml_cache_core.application.domain.model.execution.ml_execution import MlExecution
@@ -30,6 +30,7 @@ class InMemoryExecutionRepository(ExecutionRepositoryPort):
30
30
  def __init__(self, clock: ClockPort) -> None:
31
31
  self._clock = clock
32
32
  self._by_key: Dict[str, List[MlExecution]] = {}
33
+ self._tags_by_key: Dict[str, Set[str]] = {}
33
34
 
34
35
  def find_current(self, execution_key: str) -> Optional[MlExecution]:
35
36
  for execution in self._by_key.get(execution_key, []):
@@ -51,6 +52,17 @@ class InMemoryExecutionRepository(ExecutionRepositoryPort):
51
52
  prior.superseded_at = superseded_at
52
53
  history.append(stored)
53
54
 
55
+ def add_tags(self, execution_key: str, tags: List[str]) -> None:
56
+ # Tags the key's current execution; a no-op when there is none.
57
+ if not tags or self.find_current(execution_key) is None:
58
+ return
59
+ self._tags_by_key.setdefault(execution_key, set()).update(tags)
60
+
61
+ def tags_for(self, execution_key: str) -> List[str]:
62
+ if self.find_current(execution_key) is None:
63
+ return []
64
+ return sorted(self._tags_by_key.get(execution_key, set()))
65
+
54
66
  @staticmethod
55
67
  def _is_servable(execution: MlExecution) -> bool:
56
68
  """A servable execution is the current cached answer: a persisted success
@@ -86,6 +86,11 @@ CREATE TABLE IF NOT EXISTS token_usage (
86
86
  cost_usd REAL,
87
87
  raw_json TEXT NOT NULL
88
88
  );
89
+ CREATE TABLE IF NOT EXISTS execution_tags (
90
+ execution_id INTEGER NOT NULL,
91
+ tag TEXT NOT NULL,
92
+ UNIQUE(execution_id, tag)
93
+ );
89
94
  """
90
95
 
91
96
 
@@ -288,6 +293,48 @@ class SqliteExecutionRepository(ExecutionRepositoryPort):
288
293
  ),
289
294
  )
290
295
 
296
+ # -- tags (a separate annotation; never rewrites an execution) --------
297
+
298
+ @staticmethod
299
+ def _current_execution_id(connection: sqlite3.Connection, execution_key: str) -> Optional[int]:
300
+ row = connection.execute(
301
+ "SELECT id FROM executions WHERE execution_key = ? AND state = ? "
302
+ "AND output_persisted = 1 AND superseded_at IS NULL ORDER BY id DESC LIMIT 1",
303
+ (execution_key, ExecutionState.SUCCESS.value),
304
+ ).fetchone()
305
+ return int(row[0]) if row is not None else None
306
+
307
+ def add_tags(self, execution_key: str, tags: List[str]) -> None:
308
+ if not tags:
309
+ return
310
+ connection = self._connect()
311
+ try:
312
+ execution_id = self._current_execution_id(connection, execution_key)
313
+ if execution_id is None:
314
+ return
315
+ for tag in tags:
316
+ connection.execute(
317
+ "INSERT OR IGNORE INTO execution_tags (execution_id, tag) VALUES (?, ?)",
318
+ (execution_id, tag),
319
+ )
320
+ connection.commit()
321
+ finally:
322
+ connection.close()
323
+
324
+ def tags_for(self, execution_key: str) -> List[str]:
325
+ connection = self._connect()
326
+ try:
327
+ execution_id = self._current_execution_id(connection, execution_key)
328
+ if execution_id is None:
329
+ return []
330
+ rows = connection.execute(
331
+ "SELECT tag FROM execution_tags WHERE execution_id = ? ORDER BY tag",
332
+ (execution_id,),
333
+ ).fetchall()
334
+ return [tag for (tag,) in rows]
335
+ finally:
336
+ connection.close()
337
+
291
338
  # -- reconstruction ---------------------------------------------------
292
339
 
293
340
  def _load_execution(self, connection: sqlite3.Connection, row: tuple) -> MlExecution:
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  from dataclasses import dataclass, field
8
8
  from datetime import datetime
9
- from typing import List, Optional
9
+ from typing import Iterable, List, Optional
10
10
 
11
11
  from generic_ml_cache_core.application.domain.model.execution.artifact import Artifact
12
12
  from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
@@ -39,3 +39,14 @@ class MlExecution:
39
39
  token_usage: Optional[TokenUsage] = None
40
40
  failure: Optional[ExecutionFailure] = None
41
41
  superseded_at: Optional[datetime] = None
42
+
43
+
44
+ def normalize_tags(raw_tags: Iterable[str]) -> List[str]:
45
+ """Normalise user-supplied tags: trim, drop blanks, de-duplicate, sort.
46
+
47
+ Tags are metadata, never part of the cache key. Normalising at the boundary
48
+ keeps stored tags deterministic (the same set in any input order compares
49
+ equal) without interpreting their meaning — they are stored verbatim
50
+ otherwise.
51
+ """
52
+ return sorted({tag.strip() for tag in raw_tags if tag and tag.strip()})
@@ -34,6 +34,7 @@ class RunManagedLocalExecutionCommand:
34
34
  cache_mode: CacheMode = CacheMode.CACHE
35
35
  persist_output: bool = True
36
36
  record_on_error: bool = False
37
+ tags: List[str] = field(default_factory=list)
37
38
 
38
39
  @property
39
40
  def is_uncacheable(self) -> bool:
@@ -38,3 +38,15 @@ class ExecutionRepositoryPort(ABC):
38
38
  """Append a new execution. If it is a servable success, atomically
39
39
  supersede the prior current execution for the same key — the supersession
40
40
  happens here, where atomicity belongs, never in the caller."""
41
+
42
+ @abstractmethod
43
+ def add_tags(self, execution_key: str, tags: List[str]) -> None:
44
+ """Attach ``tags`` to the current execution for ``execution_key``,
45
+ idempotently — already-present tags are left untouched, new ones added.
46
+ A separate annotation layer: this never rewrites the execution record,
47
+ and is a no-op if there is no current execution for the key."""
48
+
49
+ @abstractmethod
50
+ def tags_for(self, execution_key: str) -> List[str]:
51
+ """Return the tags on the current execution for ``execution_key``, sorted;
52
+ empty if none (or no current execution)."""
@@ -93,6 +93,19 @@ class CachedMlExecutionService(ABC):
93
93
  """Whether this command cannot be cached. Default: always cacheable."""
94
94
  return False
95
95
 
96
+ def _execution_tags(self, command: CacheableExecutionCommand) -> List[str]:
97
+ """User-supplied tags to attach to executions this service records.
98
+ Metadata only — never part of the key. Default: none."""
99
+ return []
100
+
101
+ def _apply_tags(self, execution_key: str, command: CacheableExecutionCommand) -> None:
102
+ """Attach the command's tags to the current execution for this key,
103
+ idempotently (a no-op when there are none). Tags are a separate
104
+ annotation: adding one never rewrites the execution record."""
105
+ tags = self._execution_tags(command)
106
+ if tags:
107
+ self._repository.add_tags(execution_key, tags)
108
+
96
109
  # -- resolution paths -------------------------------------------------
97
110
 
98
111
  def _serve_offline(self, command: CacheableExecutionCommand, execution_key: str) -> MlExecution:
@@ -107,6 +120,7 @@ class CachedMlExecutionService(ABC):
107
120
  ) -> MlExecution:
108
121
  hydrated_execution = self._hydrate(current_execution)
109
122
  self._record_event(journal_events.HIT, execution_key, command)
123
+ self._apply_tags(execution_key, command)
110
124
  return hydrated_execution
111
125
 
112
126
  def _run_uncacheable(
@@ -139,6 +153,7 @@ class CachedMlExecutionService(ABC):
139
153
  if should_store:
140
154
  self._repository.save(execution)
141
155
  self._record_event(journal_events.RECORD, execution_key, command)
156
+ self._apply_tags(execution_key, command)
142
157
  else:
143
158
  self._record_event(journal_events.RUN, execution_key, command)
144
159
  return execution
@@ -4,8 +4,9 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from typing import Tuple
7
+ from typing import List, Tuple
8
8
 
9
+ from generic_ml_cache_core.application.domain.model.execution.ml_execution import normalize_tags
9
10
  from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
10
11
  from generic_ml_cache_core.application.domain.model.run.client_run_request import ClientRunRequest
11
12
  from generic_ml_cache_core.application.domain.model.run.client_run_result import ClientRunResult
@@ -66,6 +67,9 @@ class RunManagedLocalExecutionService(CachedMlExecutionService, RunManagedLocalE
66
67
  def _is_uncacheable(self, command: RunManagedLocalExecutionCommand) -> bool:
67
68
  return command.is_uncacheable
68
69
 
70
+ def _execution_tags(self, command: RunManagedLocalExecutionCommand) -> List[str]:
71
+ return normalize_tags(command.tags)
72
+
69
73
  @staticmethod
70
74
  def _build_client_run_request(command: RunManagedLocalExecutionCommand) -> ClientRunRequest:
71
75
  # The one allowed self-less method (AGENTS §6): the inbound-command ->
@@ -107,6 +107,31 @@ def test_save_then_find_current_returns_the_execution():
107
107
  assert found.execution_state is ExecutionState.SUCCESS
108
108
 
109
109
 
110
+ def test_add_tags_then_tags_for_returns_them_sorted():
111
+ repository = _repository()
112
+ identity = _identity()
113
+ repository.save(_execution(identity))
114
+ repository.add_tags(identity.generate_key(), ["ticket", "id-scan"])
115
+ assert repository.tags_for(identity.generate_key()) == ["id-scan", "ticket"]
116
+
117
+
118
+ def test_add_tags_is_idempotent_and_accumulates():
119
+ repository = _repository()
120
+ identity = _identity()
121
+ repository.save(_execution(identity))
122
+ key = identity.generate_key()
123
+ repository.add_tags(key, ["ticket"])
124
+ repository.add_tags(key, ["ticket", "id-scan"]) # 'ticket' already present
125
+ assert repository.tags_for(key) == ["id-scan", "ticket"]
126
+
127
+
128
+ def test_add_tags_is_a_no_op_without_a_current_execution():
129
+ repository = _repository()
130
+ identity = _identity()
131
+ repository.add_tags(identity.generate_key(), ["x"]) # nothing stored
132
+ assert repository.tags_for(identity.generate_key()) == []
133
+
134
+
110
135
  def test_find_current_returns_dehydrated_artifacts():
111
136
  repository = _repository()
112
137
  identity = _identity()
@@ -16,7 +16,10 @@ from generic_ml_cache_core.application.domain.model.execution.execution_failure
16
16
  )
17
17
  from generic_ml_cache_core.application.domain.model.execution.execution_kind import ExecutionKind
18
18
  from generic_ml_cache_core.application.domain.model.execution.execution_state import ExecutionState
19
- from generic_ml_cache_core.application.domain.model.execution.ml_execution import MlExecution
19
+ from generic_ml_cache_core.application.domain.model.execution.ml_execution import (
20
+ MlExecution,
21
+ normalize_tags,
22
+ )
20
23
  from generic_ml_cache_core.application.domain.model.usage.token_usage import TokenUsage
21
24
 
22
25
 
@@ -152,3 +155,11 @@ def test_dehydrated_execution_has_artifacts_without_content():
152
155
  )
153
156
  assert execution.artifacts[0].is_hydrated is False
154
157
  assert execution.artifacts[0].blob_key == "k"
158
+
159
+
160
+ def test_normalize_tags_trims_drops_blanks_dedupes_and_sorts():
161
+ assert normalize_tags([" beta ", "alpha", "beta", "", " ", "alpha"]) == ["alpha", "beta"]
162
+
163
+
164
+ def test_normalize_tags_of_nothing_is_empty():
165
+ assert normalize_tags([]) == []
@@ -382,3 +382,24 @@ def test_grants_change_the_identity():
382
382
  harness.use_case.execute(_command())
383
383
  harness.use_case.execute(_command(grants=["net"]))
384
384
  assert len(harness.runner.calls) == 2
385
+
386
+
387
+ # --- tags (non-identity metadata) --------------------------------------------
388
+
389
+
390
+ def test_tags_are_normalized_and_applied_to_the_recorded_execution():
391
+ harness = _Harness(ClientRunResult(exit_code=0, stdout="answer\n"))
392
+ execution = harness.use_case.execute(_command(tags=[" ticket ", "scan", "ticket", "", "scan"]))
393
+ key = execution.call_identity.generate_key()
394
+ assert harness.repository.tags_for(key) == ["scan", "ticket"]
395
+
396
+
397
+ def test_a_hit_accumulates_new_tags_onto_the_entry():
398
+ harness = _Harness()
399
+ first = harness.use_case.execute(_command(tags=["scan"]))
400
+ key = first.call_identity.generate_key()
401
+ harness.use_case.execute(_command(tags=["ticket"])) # same input -> a hit
402
+ # Tags are out of the key (still one run) and accumulate on the entry.
403
+ assert len(harness.runner.calls) == 1
404
+ assert harness.metrics.events == ["record", "hit"]
405
+ assert harness.repository.tags_for(key) == ["scan", "ticket"]
@@ -173,6 +173,31 @@ def test_token_usage_round_trips(tmp_path):
173
173
  assert restored == usage
174
174
 
175
175
 
176
+ def test_add_tags_then_tags_for_returns_them_sorted(tmp_path):
177
+ repository = _repository(tmp_path)
178
+ identity = _managed_identity()
179
+ repository.save(_execution(identity))
180
+ repository.add_tags(identity.generate_key(), ["ticket", "id-scan"])
181
+ assert repository.tags_for(identity.generate_key()) == ["id-scan", "ticket"]
182
+
183
+
184
+ def test_add_tags_is_idempotent_and_accumulates(tmp_path):
185
+ repository = _repository(tmp_path)
186
+ identity = _managed_identity()
187
+ repository.save(_execution(identity))
188
+ key = identity.generate_key()
189
+ repository.add_tags(key, ["ticket"])
190
+ repository.add_tags(key, ["ticket", "id-scan"]) # 'ticket' already present
191
+ assert repository.tags_for(key) == ["id-scan", "ticket"]
192
+
193
+
194
+ def test_add_tags_is_a_no_op_without_a_current_execution(tmp_path):
195
+ repository = _repository(tmp_path)
196
+ identity = _managed_identity()
197
+ repository.add_tags(identity.generate_key(), ["x"]) # nothing stored
198
+ assert repository.tags_for(identity.generate_key()) == []
199
+
200
+
176
201
  def test_failure_round_trips_in_history(tmp_path):
177
202
  repository = _repository(tmp_path)
178
203
  identity = _managed_identity()