openframe-core 3.6.0__tar.gz → 3.7.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 (157) hide show
  1. {openframe_core-3.6.0 → openframe_core-3.7.0}/.github/CHANGELOG.md +128 -1
  2. {openframe_core-3.6.0 → openframe_core-3.7.0}/PKG-INFO +1 -1
  3. openframe_core-3.7.0/openframe/core/remote/__init__.py +23 -0
  4. openframe_core-3.7.0/openframe/core/remote/client.py +135 -0
  5. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/__init__.py +6 -0
  6. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/__init__.py +6 -0
  7. openframe_core-3.7.0/openframe/core/testing/contracts/remote.py +79 -0
  8. {openframe_core-3.6.0 → openframe_core-3.7.0}/pyproject.toml +1 -1
  9. {openframe_core-3.6.0 → openframe_core-3.7.0}/.github/requirements/docs.txt +0 -0
  10. {openframe_core-3.6.0 → openframe_core-3.7.0}/.github/requirements/test.txt +0 -0
  11. {openframe_core-3.6.0 → openframe_core-3.7.0}/.github/workflows/app-test.yml +0 -0
  12. {openframe_core-3.6.0 → openframe_core-3.7.0}/.github/workflows/auto-docs.yml +0 -0
  13. {openframe_core-3.6.0 → openframe_core-3.7.0}/.github/workflows/python-build.yml +0 -0
  14. {openframe_core-3.6.0 → openframe_core-3.7.0}/.gitignore +0 -0
  15. {openframe_core-3.6.0 → openframe_core-3.7.0}/.vscode/settings.json +0 -0
  16. {openframe_core-3.6.0 → openframe_core-3.7.0}/LICENSE +0 -0
  17. {openframe_core-3.6.0 → openframe_core-3.7.0}/README.md +0 -0
  18. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/assets/images/banner.png +0 -0
  19. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/assets/images/favicon.png +0 -0
  20. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/assets/images/logo-dark.png +0 -0
  21. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/assets/images/logo-light.png +0 -0
  22. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/assets/javascripts/extra.js +0 -0
  23. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/assets/stylesheets/extra.css +0 -0
  24. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/cicd/pipeline.md +0 -0
  25. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/getting-started/index.md +0 -0
  26. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/config.md +0 -0
  27. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/exceptions.md +0 -0
  28. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/health.md +0 -0
  29. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/inbound.md +0 -0
  30. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/index.md +0 -0
  31. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/middleware.md +0 -0
  32. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/plugins.md +0 -0
  33. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/ports.md +0 -0
  34. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/runtime.md +0 -0
  35. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/telemetry.md +0 -0
  36. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/testing.md +0 -0
  37. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/code/modules/tracing.md +0 -0
  38. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/developer-guide/debugging.md +0 -0
  39. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/developer-guide/explore.md +0 -0
  40. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/developer-guide/first-change.md +0 -0
  41. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/developer-guide/how-it-works.md +0 -0
  42. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/developer-guide/local-dev.md +0 -0
  43. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/developer-guide/quick-start/index.md +0 -0
  44. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/index.md +0 -0
  45. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/adrs/adr-001-namespace.md +0 -0
  46. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/adrs/adr-002-hexagonal.md +0 -0
  47. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/adrs/adr-003-async.md +0 -0
  48. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/adrs/adr-004-pydantic.md +0 -0
  49. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/adrs/adr-005-middleware.md +0 -0
  50. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/adrs/adr-006-unified-port-lifecycle.md +0 -0
  51. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/adrs/adr-007-schema-contracts.md +0 -0
  52. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/adrs/index.md +0 -0
  53. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/capability-taxonomy.md +0 -0
  54. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/component-lifecycle.md +0 -0
  55. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/data-flows.md +0 -0
  56. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/design-decisions.md +0 -0
  57. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/error-taxonomy.md +0 -0
  58. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/package-journey/adapter-lifecycle.md +0 -0
  59. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/package-journey/error-propagation.md +0 -0
  60. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/package-journey/index.md +0 -0
  61. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/package-journey/template-wiring.md +0 -0
  62. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/package-journey/tracing-flow.md +0 -0
  63. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/architecture/system-architecture.md +0 -0
  64. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/deployment/cicd.md +0 -0
  65. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/deployment/environments.md +0 -0
  66. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/deployment/secrets-and-config.md +0 -0
  67. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/infrastructure/networking.md +0 -0
  68. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/infrastructure/platform.md +0 -0
  69. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/infrastructure/storage.md +0 -0
  70. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/operations/index.md +0 -0
  71. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/operations/monitoring.md +0 -0
  72. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/operations/runbooks/adapter-connection-failure.md +0 -0
  73. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/operations/runbooks/modal-cold-start-spike.md +0 -0
  74. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/operations/runbooks/postgres-pool-exhausted.md +0 -0
  75. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/operations/runbooks/queue-consumer-stuck.md +0 -0
  76. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/operations/runbooks/redis-connection-lost.md +0 -0
  77. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/operations/runbooks/telemetry-export-failing.md +0 -0
  78. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/overview/glossary.md +0 -0
  79. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/overview/roadmap.md +0 -0
  80. {openframe_core-3.6.0 → openframe_core-3.7.0}/docs/technical/overview/system-overview.md +0 -0
  81. {openframe_core-3.6.0 → openframe_core-3.7.0}/mkdocs.yml +0 -0
  82. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/__init__.py +0 -0
  83. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/__init__.py +0 -0
  84. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/config/__init__.py +0 -0
  85. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/config/settings.py +0 -0
  86. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/exceptions/__init__.py +0 -0
  87. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/exceptions/adapter.py +0 -0
  88. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/exceptions/base.py +0 -0
  89. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/exceptions/codes.py +0 -0
  90. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/exceptions/plugin.py +0 -0
  91. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/inbound/__init__.py +0 -0
  92. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/inbound/command.py +0 -0
  93. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/inbound/context.py +0 -0
  94. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/inbound/query.py +0 -0
  95. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/inbound/usecase.py +0 -0
  96. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/middleware/__init__.py +0 -0
  97. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/middleware/telemetry.py +0 -0
  98. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/middleware/types.py +0 -0
  99. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/plugins/__init__.py +0 -0
  100. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/plugins/registry.py +0 -0
  101. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/__init__.py +0 -0
  102. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/capability.py +0 -0
  103. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/context.py +0 -0
  104. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/health.py +0 -0
  105. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/identity.py +0 -0
  106. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/lifecycle.py +0 -0
  107. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/outbound/__init__.py +0 -0
  108. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/outbound/consumer.py +0 -0
  109. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/outbound/producer.py +0 -0
  110. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/outbound/repository.py +0 -0
  111. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/ports/port.py +0 -0
  112. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/resilience/__init__.py +0 -0
  113. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/resilience/circuit_breaker.py +0 -0
  114. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/runtime/__init__.py +0 -0
  115. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/runtime/bootstrap.py +0 -0
  116. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/schemas/__init__.py +0 -0
  117. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/telemetry/__init__.py +0 -0
  118. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/telemetry/setup.py +0 -0
  119. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/consumer.py +0 -0
  120. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/graph_store.py +0 -0
  121. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/inbound.py +0 -0
  122. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/lifecycle.py +0 -0
  123. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/port.py +0 -0
  124. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/producer.py +0 -0
  125. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/repository.py +0 -0
  126. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/contracts/vector_store.py +0 -0
  127. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/fakes/__init__.py +0 -0
  128. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/fakes/consumer.py +0 -0
  129. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/fakes/graph_store.py +0 -0
  130. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/fakes/inbound.py +0 -0
  131. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/fakes/producer.py +0 -0
  132. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/fakes/repository.py +0 -0
  133. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/fakes/vector_store.py +0 -0
  134. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/testing/fixtures.py +0 -0
  135. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/tracing/__init__.py +0 -0
  136. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/tracing/propagation.py +0 -0
  137. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/core/tracing/proxy.py +0 -0
  138. {openframe_core-3.6.0 → openframe_core-3.7.0}/openframe/py.typed +0 -0
  139. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/conftest.py +0 -0
  140. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_config.py +0 -0
  141. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_exceptions.py +0 -0
  142. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_inbound.py +0 -0
  143. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_inbound_contracts.py +0 -0
  144. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_middleware.py +0 -0
  145. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_middleware_types.py +0 -0
  146. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_plugins.py +0 -0
  147. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_ports.py +0 -0
  148. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_ports_primitives.py +0 -0
  149. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_record_error.py +0 -0
  150. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_resilience.py +0 -0
  151. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_runtime.py +0 -0
  152. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_schemas.py +0 -0
  153. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_shutdown_telemetry.py +0 -0
  154. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_telemetry_sidecar.py +0 -0
  155. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_testing_fakes.py +0 -0
  156. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_tracing.py +0 -0
  157. {openframe_core-3.6.0 → openframe_core-3.7.0}/tests/test_tracing_propagation.py +0 -0
@@ -11,6 +11,130 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ---
13
13
 
14
+ ## v3.7.0 — 2026-10-11
15
+
16
+ ### Added
17
+
18
+ - **`openframe.core.remote`** — `RemoteCommandClient[TIn]`/
19
+ `RemoteQueryClient[TIn, TOut]`, the outbound counterparts of
20
+ `openframe.core.inbound`'s `CommandHandler`/`QueryHandler`, for
21
+ `openframe-protocol`'s M1b (HTTP client, service-to-service calls).
22
+ Structurally identical `execute(input, context) -> result` shape to
23
+ the inbound pair, but deliberately separate Protocol classes in their
24
+ own module — direction matters even when the signature doesn't
25
+ differ, and conflating "something calls into me" with "I call out to
26
+ something" under one name would erase the distinction this
27
+ ecosystem's whole organising principle rests on. No `BasePort`/
28
+ `Identity`/`Capability` involvement: `openframe-protocol`'s own design
29
+ review (draft 8, decision D10) found no dynamic "give me whichever
30
+ remote client fills this role" lookup use case exists — a caller
31
+ always knows exactly which service it's calling — so none of the
32
+ machinery justifying `Capability`-based registry lookup applies here.
33
+ Wired by direct construction in a service's own `bootstrap`, the same
34
+ way `CommandHandler`/`QueryHandler` themselves are never looked up by
35
+ capability.
36
+ - **`openframe.core.testing.RemoteCommandClientContractTests`/
37
+ `RemoteQueryClientContractTests`** — extend the existing
38
+ `CommandHandlerContractTests`/`QueryHandlerContractTests` rather than
39
+ duplicating their test bodies (only the `isinstance` assertion
40
+ differs). No new fakes needed either — `SpyCommandHandler`/
41
+ `SpyQueryHandler` already satisfy the new protocols structurally,
42
+ confirmed directly rather than assumed.
43
+ - 12 new tests. Full suite: 422 passing (up from 410).
44
+
45
+ ---
46
+
47
+ ## v3.6.0 — 2026-10-09
48
+
49
+ ### Added
50
+
51
+ - **`openframe.core.ports.BaseGraphStore[T]`** — a new outbound port for
52
+ graph-store adapters (FalkorDB and similar), defined in
53
+ `ports/outbound/repository.py` alongside `BaseRepository`/
54
+ `BaseVectorStore` — same rationale as `BaseVectorStore` for staying in
55
+ this file rather than its own. Extends `BaseRepository[T]` directly:
56
+ a graph database's nodes are genuinely id-addressable
57
+ (create/get/update/delete map onto real `CREATE`/`MATCH ... WHERE
58
+ n.<key> = $id`/`SET`/`DETACH DELETE` operations), provided the adapter
59
+ addresses nodes by an application-level unique property (with a
60
+ backing index/constraint the adapter itself must create) rather than
61
+ a backend-internal node identity. FalkorDB's own `id()` function is
62
+ explicitly documented upstream as unstable — deleted nodes' higher
63
+ internal ids can be migrated down to fill vacated lower ones — which
64
+ is exactly why adapters must key off their own property, not a reason
65
+ graph databases don't fit this port. The one operation with no CRUD
66
+ equivalent is pattern-matching/traversal, so `BaseGraphStore` adds
67
+ exactly one new method, `traverse(query, params) -> list[T]`, which
68
+ also serves as the only way to create or query relationships/edges —
69
+ this port deliberately does not give edges their own method, the same
70
+ way it does not invent a bespoke query language. Marked
71
+ `.. stability: experimental`, same as `BaseVectorStore`;
72
+ `BaseRepository` itself is unaffected and remains stable.
73
+ - **`openframe.core.testing.GraphStoreContractTests`** — extends
74
+ `RepositoryContractTests` (inherits the full CRUD suite), adds four
75
+ `traverse()`-specific tests plus a `BaseGraphStore` protocol
76
+ conformance check. Deliberately query-language-agnostic: rather than
77
+ hardcoding Cypher syntax, it asks a `traverse_query_for(entity)`
78
+ fixture for a `(query, params)` pair in whatever language the adapter
79
+ under test actually speaks — mirroring how `VectorStoreContractTests`
80
+ asks for an `entity_vector` extractor instead of assuming a specific
81
+ similarity metric.
82
+ - **`openframe.core.testing.InMemoryGraphStore`** — the matching fake,
83
+ extending `InMemoryRepository`. Its own `traverse()` is a toy lookup
84
+ (recognizes a `params["id"]` convention; the `query` string itself is
85
+ accepted but not interpreted) proving the contract's *shape*, not a
86
+ query-engine simulation.
87
+ - 27 new tests (23 inherited `RepositoryContractTests`/`PortContractTests`
88
+ assertions re-run against `InMemoryGraphStore`, plus 4
89
+ `traverse()`-specific tests). Full suite: 410 passing (up from 383).
90
+
91
+ ---
92
+
93
+ ## v3.5.0 — 2026-10-08
94
+
95
+ ### Added
96
+
97
+ - **`openframe.core.ports.BaseVectorStore[T]`** — a new outbound port for
98
+ vector-store adapters (Milvus, Qdrant, ChromaDB), defined in
99
+ `ports/outbound/repository.py` alongside `BaseRepository` (not its own
100
+ file — deliberately, so the "why this extends `BaseRepository`"
101
+ rationale lives next to both classes it concerns). Extends
102
+ `BaseRepository[T]` directly rather than sitting as a disjoint sibling
103
+ port — most vector databases are genuinely id-addressable
104
+ (insert/upsert-with-id, fetch-by-id, delete-by-id, list/scroll), so the
105
+ five inherited CRUD methods are real operations for them, not fakes
106
+ forced to satisfy an interface. Adds exactly one new method,
107
+ `search(query_vector, k) -> list[T]`, the one operation with no
108
+ equivalent in `BaseRepository`. A vector store satisfying
109
+ `BaseVectorStore` also satisfies `BaseRepository` for free. Explicitly
110
+ does **not** target FAISS (a bare in-process index with no native
111
+ id→metadata store, and some index types that don't support deletion
112
+ without a full rebuild) or FalkorDB (fundamentally graph-oriented, not
113
+ id-addressable the same way) — both were evaluated and excluded;
114
+ graph databases need their own separate port design, not a forced fit
115
+ here.
116
+ - **`openframe.core.testing.contracts.VectorStoreContractTests`** — the
117
+ matching contract-test mixin, extending `RepositoryContractTests`
118
+ (inherits all its CRUD assertions, adds four search-specific ones).
119
+ Deliberately metric-agnostic: every search assertion uses an
120
+ exact-match query vector rather than asserting a specific similarity
121
+ score or ranking among near-ties, since backends default to different
122
+ metrics (cosine, L2, dot product) that would legitimately rank
123
+ approximately-similar vectors differently — an exact match ranks
124
+ first under any reasonable metric, so that's what the contract tests
125
+ rely on.
126
+ - **`openframe.core.testing.fakes.InMemoryVectorStore`** — the matching
127
+ fake, extending `InMemoryRepository` unchanged and adding a
128
+ brute-force Euclidean-distance `search()`. Proven against the full
129
+ `VectorStoreContractTests` suite in this release's own test suite
130
+ (27 new tests: 23 inherited CRUD/identity/lifecycle + 4 search-specific).
131
+ - All three pieces are `experimental` — new port, new contract-test
132
+ mixin, new fake, all added together ahead of the first real adapter
133
+ packages that will implement `BaseVectorStore` (Milvus, Qdrant,
134
+ ChromaDB).
135
+
136
+ ---
137
+
14
138
  ## v3.4.0 — 2026-09-28
15
139
 
16
140
  ### Added
@@ -425,7 +549,10 @@ Dev dependencies: `pytest>=8.0`, `pytest-asyncio>=0.23`, `httpx>=0.27`.
425
549
 
426
550
  ---
427
551
 
428
- [Unreleased]: https://github.com/openframe-org/openframe-core/compare/v3.4.0...HEAD
552
+ [Unreleased]: https://github.com/openframe-org/openframe-core/compare/v3.7.0...HEAD
553
+ [3.7.0]: https://github.com/openframe-org/openframe-core/compare/v3.6.0...v3.7.0
554
+ [3.6.0]: https://github.com/openframe-org/openframe-core/compare/v3.5.0...v3.6.0
555
+ [3.5.0]: https://github.com/openframe-org/openframe-core/compare/v3.4.0...v3.5.0
429
556
  [3.4.0]: https://github.com/openframe-org/openframe-core/compare/v3.3.0...v3.4.0
430
557
  [3.3.0]: https://github.com/openframe-org/openframe-core/compare/v3.2.1...v3.3.0
431
558
  [3.2.1]: https://github.com/openframe-org/openframe-core/compare/v3.2.0...v3.2.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: openframe-core
3
- Version: 3.6.0
3
+ Version: 3.7.0
4
4
  Summary: OpenFrame Microservice Suite - platform kernel. Port contracts, testing utilities, plugin registry, and composition root.
5
5
  Project-URL: Homepage, https://github.com/Furious-Meteors/openframe-core
6
6
  Project-URL: Documentation, https://furious-meteors.github.io/openframe-core/
@@ -0,0 +1,23 @@
1
+ """
2
+ openframe.core.remote
3
+ ========================
4
+ Outbound counterparts of openframe.core.inbound's CommandHandler/
5
+ QueryHandler — contracts for a service calling *another service built on
6
+ this platform*, as opposed to `openframe.core.ports.outbound` (third-party
7
+ backend adapters, Capability-tagged) or `openframe.core.inbound`
8
+ (callers reaching into this service).
9
+
10
+ Added for openframe-protocol's M1b (HTTP client) — see that repo's design
11
+ spec draft 8, section 5.6, decision D10. No BasePort/Capability
12
+ involvement; see `client.py`'s own docstring for why.
13
+
14
+ .. stability: experimental
15
+ New alongside the first concrete implementation
16
+ (`openframe-protocol-http`'s `HttpRemoteQueryClient`/
17
+ `HttpRemoteCommandClient`). API may change based on real usage.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from openframe.core.remote.client import RemoteCommandClient, RemoteQueryClient
22
+
23
+ __all__ = ["RemoteCommandClient", "RemoteQueryClient"]
@@ -0,0 +1,135 @@
1
+ """
2
+ openframe/core/remote/client.py
3
+ ==================================
4
+ RemoteCommandClient, RemoteQueryClient — the outbound counterparts of
5
+ openframe.core.inbound's CommandHandler/QueryHandler (openframe-protocol
6
+ draft 8, section 5.6; D10).
7
+
8
+ Structurally identical to CommandHandler/QueryHandler — same
9
+ `execute(input, context) -> result` shape — but deliberately a separate
10
+ pair of Protocol classes in their own module, not aliases. Two reasons:
11
+
12
+ 1. Direction matters even when the signature doesn't differ. This
13
+ ecosystem's entire organising principle (see openframe-protocol's own
14
+ design history) is driving side vs. driven side, and conflating a
15
+ "something calls into me" contract with a "I call out to something"
16
+ contract under one name would erase exactly the distinction that
17
+ took real design work to land on.
18
+ 2. `isinstance(x, RemoteQueryClient)` should read as "this is a remote
19
+ call" at the call site, not "this happens to satisfy CommandHandler's
20
+ shape." Structural typing doesn't require the names to differ, but
21
+ the names carry real information a reader relies on.
22
+
23
+ No BasePort/Identity/Capability involvement, by design — see draft 8
24
+ section 4's D10 resolution: there is no dynamic "give me whichever
25
+ remote client fills this role" lookup use case (a caller always knows
26
+ exactly which service it's calling), so none of the machinery that
27
+ justifies Capability-based registry lookup applies here. These are
28
+ wired by direct construction in `bootstrap`, the same way `CommandHandler`/
29
+ `QueryHandler` themselves are never looked up by Capability.
30
+
31
+ Dependency order:
32
+ inbound/context → ports/context
33
+ remote/client → inbound/context (reuses the same RequestContext —
34
+ one request's identity threads through both
35
+ directions of a call it triggers, matching how
36
+ RequestContext is already shared by inbound).
37
+ """
38
+ from __future__ import annotations
39
+
40
+ from typing import Protocol, TypeVar, runtime_checkable
41
+
42
+ from openframe.core.inbound.context import RequestContext
43
+
44
+ __all__ = ["RemoteCommandClient", "RemoteQueryClient"]
45
+
46
+ TInput = TypeVar("TInput")
47
+ TOutput = TypeVar("TOutput")
48
+
49
+
50
+ @runtime_checkable
51
+ class RemoteCommandClient(Protocol[TInput]):
52
+ """
53
+ Outbound write-side contract — calls another service to mutate its
54
+ state. The write-side counterpart of
55
+ :class:`~openframe.core.inbound.command.CommandHandler`, for the
56
+ opposite direction.
57
+
58
+ Adapters/clients satisfy this protocol structurally — no inheritance
59
+ needed.
60
+
61
+ Type parameter:
62
+ TInput: The command type this client sends.
63
+
64
+ Runtime check::
65
+
66
+ isinstance(client, RemoteCommandClient) # ✓ works
67
+ isinstance(client, RemoteCommandClient[str]) # ✗ raises TypeError
68
+ """
69
+
70
+ async def execute(
71
+ self,
72
+ command: TInput,
73
+ context: RequestContext | None = None,
74
+ ) -> None:
75
+ """
76
+ Send the command to the remote service.
77
+
78
+ Args:
79
+ command: The command to send.
80
+ context: Optional request context — a real implementation
81
+ propagates `context.correlation_id` onward (e.g. as
82
+ an outgoing `traceparent`), threading one logical
83
+ request's identity through every service it touches.
84
+
85
+ Raises:
86
+ An `AdapterError` subclass on failure — a remote call is, from
87
+ the caller's perspective, exactly as fallible as a database
88
+ call, and should surface through the same exception family
89
+ so callers don't need a separate catch clause for "this
90
+ dependency happens to be another service" versus "this
91
+ dependency happens to be a database."
92
+ """
93
+ ...
94
+
95
+
96
+ @runtime_checkable
97
+ class RemoteQueryClient(Protocol[TInput, TOutput]):
98
+ """
99
+ Outbound read-side contract — calls another service and returns its
100
+ result. The read-side counterpart of
101
+ :class:`~openframe.core.inbound.query.QueryHandler`, for the
102
+ opposite direction.
103
+
104
+ Type parameters:
105
+ TInput: The query type this client sends.
106
+ TOutput: The result type this client returns.
107
+
108
+ Runtime check::
109
+
110
+ isinstance(client, RemoteQueryClient) # ✓ works
111
+ isinstance(client, RemoteQueryClient[str, int]) # ✗ raises TypeError
112
+ """
113
+
114
+ async def execute(
115
+ self,
116
+ query: TInput,
117
+ context: RequestContext | None = None,
118
+ ) -> TOutput:
119
+ """
120
+ Send the query to the remote service and return its result.
121
+
122
+ Args:
123
+ query: The query to send.
124
+ context: Optional request context, propagated onward — see
125
+ `RemoteCommandClient.execute`'s own docstring.
126
+
127
+ Returns:
128
+ The remote service's result.
129
+
130
+ Raises:
131
+ An `AdapterError` subclass on failure — see
132
+ `RemoteCommandClient.execute`'s own docstring for why this
133
+ family, not a bespoke one.
134
+ """
135
+ ...
@@ -42,6 +42,10 @@ from openframe.core.testing.contracts.inbound import (
42
42
  from openframe.core.testing.contracts.lifecycle import LifecycleContractTests
43
43
  from openframe.core.testing.contracts.port import PortContractTests
44
44
  from openframe.core.testing.contracts.producer import ProducerContractTests
45
+ from openframe.core.testing.contracts.remote import (
46
+ RemoteCommandClientContractTests,
47
+ RemoteQueryClientContractTests,
48
+ )
45
49
  from openframe.core.testing.contracts.repository import RepositoryContractTests
46
50
  from openframe.core.testing.contracts.vector_store import VectorStoreContractTests
47
51
  from openframe.core.testing.fakes.consumer import FakeConsumer
@@ -74,4 +78,6 @@ __all__ = [
74
78
  "UseCaseContractTests",
75
79
  "CommandHandlerContractTests",
76
80
  "QueryHandlerContractTests",
81
+ "RemoteCommandClientContractTests",
82
+ "RemoteQueryClientContractTests",
77
83
  ]
@@ -37,6 +37,10 @@ from openframe.core.testing.contracts.inbound import (
37
37
  from openframe.core.testing.contracts.lifecycle import LifecycleContractTests
38
38
  from openframe.core.testing.contracts.port import PortContractTests
39
39
  from openframe.core.testing.contracts.producer import ProducerContractTests
40
+ from openframe.core.testing.contracts.remote import (
41
+ RemoteCommandClientContractTests,
42
+ RemoteQueryClientContractTests,
43
+ )
40
44
  from openframe.core.testing.contracts.repository import RepositoryContractTests
41
45
  from openframe.core.testing.contracts.vector_store import VectorStoreContractTests
42
46
 
@@ -51,4 +55,6 @@ __all__ = [
51
55
  "UseCaseContractTests",
52
56
  "CommandHandlerContractTests",
53
57
  "QueryHandlerContractTests",
58
+ "RemoteCommandClientContractTests",
59
+ "RemoteQueryClientContractTests",
54
60
  ]
@@ -0,0 +1,79 @@
1
+ """
2
+ openframe/core/testing/contracts/remote.py
3
+ =============================================
4
+ RemoteCommandClientContractTests, RemoteQueryClientContractTests —
5
+ reusable pytest base classes for `openframe.core.remote`'s outbound
6
+ contracts.
7
+
8
+ Extends `CommandHandlerContractTests`/`QueryHandlerContractTests` rather
9
+ than duplicating their test bodies — `RemoteCommandClient`/
10
+ `RemoteQueryClient` have the identical `execute()` shape (see
11
+ `remote/client.py`'s own docstring for why they're still separate
12
+ Protocol classes despite that). Only the protocol-conformance assertion
13
+ differs; every other test (return-type shape, context handling) is
14
+ inherited unchanged.
15
+
16
+ No top-level pytest import — importable without pytest installed,
17
+ matching every other contract module's own convention.
18
+
19
+ Dependency order:
20
+ testing/contracts/remote → testing/contracts/inbound, remote
21
+
22
+ Subclass usage::
23
+
24
+ class TestMyRemoteQueryClient(RemoteQueryClientContractTests):
25
+ @pytest.fixture
26
+ def query_handler(self) -> MyRemoteQueryClient:
27
+ return MyRemoteQueryClient()
28
+ """
29
+ from __future__ import annotations
30
+
31
+ from openframe.core.testing.contracts.inbound import (
32
+ CommandHandlerContractTests,
33
+ QueryHandlerContractTests,
34
+ )
35
+
36
+ __all__ = ["RemoteCommandClientContractTests", "RemoteQueryClientContractTests"]
37
+
38
+
39
+ class RemoteCommandClientContractTests(CommandHandlerContractTests):
40
+ """
41
+ Reusable pytest base class for
42
+ :class:`~openframe.core.remote.RemoteCommandClient` contract tests.
43
+
44
+ Subclasses must provide the same `command_handler` fixture
45
+ `CommandHandlerContractTests` itself requires — the fixture name is
46
+ unchanged, only the protocol being checked is.
47
+
48
+ .. stability: experimental
49
+ """
50
+
51
+ async def test_satisfies_command_handler_protocol(self, command_handler) -> None:
52
+ """Overridden: checks RemoteCommandClient, not CommandHandler."""
53
+ from openframe.core.remote import RemoteCommandClient
54
+
55
+ assert isinstance(command_handler, RemoteCommandClient), (
56
+ f"{type(command_handler).__name__} does not satisfy RemoteCommandClient. "
57
+ "Ensure it has a matching async 'execute' method."
58
+ )
59
+
60
+
61
+ class RemoteQueryClientContractTests(QueryHandlerContractTests):
62
+ """
63
+ Reusable pytest base class for
64
+ :class:`~openframe.core.remote.RemoteQueryClient` contract tests.
65
+
66
+ Subclasses must provide the same `query_handler`/`make_query`
67
+ fixtures `QueryHandlerContractTests` itself requires.
68
+
69
+ .. stability: experimental
70
+ """
71
+
72
+ async def test_satisfies_query_handler_protocol(self, query_handler) -> None:
73
+ """Overridden: checks RemoteQueryClient, not QueryHandler."""
74
+ from openframe.core.remote import RemoteQueryClient
75
+
76
+ assert isinstance(query_handler, RemoteQueryClient), (
77
+ f"{type(query_handler).__name__} does not satisfy RemoteQueryClient. "
78
+ "Ensure it has a matching async 'execute' method."
79
+ )
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openframe-core"
7
- version = "3.6.0"
7
+ version = "3.7.0"
8
8
  description = "OpenFrame Microservice Suite - platform kernel. Port contracts, testing utilities, plugin registry, and composition root."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes