claude-smart 0.2.47 → 0.2.49

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 (139) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +6 -2
  3. package/bin/claude-smart.js +289 -56
  4. package/package.json +1 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
  8. package/plugin/dashboard/package-lock.json +61 -390
  9. package/plugin/dashboard/package.json +2 -2
  10. package/plugin/opencode/dist/server.mjs +60 -2
  11. package/plugin/opencode/server.mts +64 -2
  12. package/plugin/pyproject.toml +2 -2
  13. package/plugin/scripts/_lib.sh +58 -6
  14. package/plugin/scripts/backend-service.sh +15 -10
  15. package/plugin/scripts/codex-hook.js +16 -4
  16. package/plugin/scripts/dashboard-service.sh +1 -1
  17. package/plugin/scripts/ensure-plugin-root.sh +106 -8
  18. package/plugin/scripts/opencode-claude-compat.js +8 -1
  19. package/plugin/scripts/smart-install.sh +101 -20
  20. package/plugin/src/README.md +1 -1
  21. package/plugin/src/claude_smart/cli.py +79 -29
  22. package/plugin/src/claude_smart/env_config.py +53 -11
  23. package/plugin/uv.lock +5 -5
  24. package/plugin/vendor/reflexio/.env.example +7 -0
  25. package/plugin/vendor/reflexio/pyproject.toml +2 -1
  26. package/plugin/vendor/reflexio/reflexio/README.md +8 -5
  27. package/plugin/vendor/reflexio/reflexio/cli/README.md +3 -0
  28. package/plugin/vendor/reflexio/reflexio/client/client.py +40 -6
  29. package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
  30. package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
  31. package/plugin/vendor/reflexio/reflexio/lib/_search.py +15 -11
  32. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +18 -0
  33. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
  34. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +24 -3
  35. package/plugin/vendor/reflexio/reflexio/server/README.md +25 -8
  36. package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
  37. package/plugin/vendor/reflexio/reflexio/server/api.py +148 -3277
  38. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
  39. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
  40. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
  41. package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
  42. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
  43. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
  44. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
  45. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
  46. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
  47. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
  48. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +72 -1817
  49. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +56 -4
  50. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +183 -1
  51. package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
  52. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
  53. package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
  54. package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
  55. package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
  56. package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
  57. package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
  58. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
  59. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
  60. package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +320 -0
  61. package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
  62. package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
  63. package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
  64. package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
  65. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
  66. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
  67. package/plugin/vendor/reflexio/reflexio/server/services/README.md +3 -2
  68. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
  69. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
  70. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
  71. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
  72. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
  73. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
  74. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
  75. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
  76. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
  77. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/__init__.py +13 -0
  78. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
  79. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +193 -0
  80. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +24 -41
  81. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +335 -113
  82. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +362 -81
  83. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -490
  84. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
  85. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
  86. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
  87. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
  88. package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +44 -22
  89. package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
  90. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +31 -4
  91. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
  92. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +196 -353
  93. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +0 -1513
  94. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +379 -0
  95. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +17 -6
  96. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1281
  97. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
  98. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
  99. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
  100. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
  101. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
  102. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
  103. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
  104. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
  105. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +216 -0
  106. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
  107. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
  108. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
  109. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
  110. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
  111. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
  112. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +22 -10
  113. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +8 -3
  114. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +25 -12
  115. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
  116. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +308 -0
  117. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +920 -0
  118. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -8
  120. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
  121. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_commit_scope.py +9 -0
  122. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +219 -0
  123. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
  124. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
  125. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
  126. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
  127. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
  128. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
  129. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
  130. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
  131. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
  132. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
  133. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
  134. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
  135. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
  136. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +95 -0
  137. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +60 -80
  138. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
  139. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +0 -148
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from reflexio.models.api_schema.domain.governance import (
6
+ AuditEvent,
7
+ PurgeOperation,
8
+ SubjectWriteBarrier,
9
+ )
10
+
11
+
12
+ class SubjectBarrierMixin(ABC):
13
+ """Backend-neutral subject-erasure-barrier store contract.
14
+
15
+ Extracted verbatim from ``_governance.py`` (the SubjectBarrier bucket). The
16
+ residual ``GovernanceMixin`` ABC stays composed alongside this mixin and
17
+ holds the remaining rebuild-hide / governance-erase-execution / purge-complete
18
+ abstract methods.
19
+ """
20
+
21
+ @abstractmethod
22
+ def begin_subject_erasure_barrier(
23
+ self, subject_ref: str, purge_id: str
24
+ ) -> SubjectWriteBarrier:
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def assert_subject_writable(self, subject_ref: str) -> None:
29
+ raise NotImplementedError
30
+
31
+ @abstractmethod
32
+ def complete_subject_erasure_barrier_after_empty_check(
33
+ self, purge_id: str, audit_event: AuditEvent
34
+ ) -> PurgeOperation:
35
+ raise NotImplementedError
36
+
37
+ @abstractmethod
38
+ def fail_subject_erasure_barrier(
39
+ self,
40
+ subject_ref: str,
41
+ purge_id: str,
42
+ error_code: str,
43
+ error_detail: str,
44
+ ) -> SubjectWriteBarrier:
45
+ raise NotImplementedError
46
+
47
+ @abstractmethod
48
+ def get_subject_write_barrier(self, subject_ref: str) -> SubjectWriteBarrier | None:
49
+ raise NotImplementedError
@@ -0,0 +1,9 @@
1
+ from ._interaction_store import InteractionStoreMixin
2
+ from ._profile_store import ProfileStoreMixin
3
+ from ._search import ProfileSearchMixin
4
+
5
+ __all__ = [
6
+ "InteractionStoreMixin",
7
+ "ProfileSearchMixin",
8
+ "ProfileStoreMixin",
9
+ ]
@@ -0,0 +1,95 @@
1
+ from abc import abstractmethod
2
+
3
+ from reflexio.models.api_schema.domain import (
4
+ DeleteUserInteractionRequest,
5
+ Interaction,
6
+ )
7
+
8
+
9
+ class InteractionStoreMixin:
10
+ """Mixin for interaction-store CRUD methods."""
11
+
12
+ @abstractmethod
13
+ def get_all_interactions(self, limit: int = 100) -> list[Interaction]:
14
+ raise NotImplementedError
15
+
16
+ @abstractmethod
17
+ def get_user_interaction(self, user_id: str) -> list[Interaction]:
18
+ raise NotImplementedError
19
+
20
+ @abstractmethod
21
+ def get_all_user_ids(self) -> list[str]:
22
+ """Return distinct user IDs that have stored interactions."""
23
+ raise NotImplementedError
24
+
25
+ @abstractmethod
26
+ def add_user_interaction(self, user_id: str, interaction: Interaction) -> None:
27
+ raise NotImplementedError
28
+
29
+ @abstractmethod
30
+ def add_user_interactions_bulk(
31
+ self,
32
+ user_id: str,
33
+ interactions: list[Interaction],
34
+ *,
35
+ embeddings_prepared: bool = False,
36
+ ) -> None:
37
+ """Add multiple user interactions with batched embedding generation.
38
+
39
+ Args:
40
+ user_id: The user ID
41
+ interactions: List of interactions to add
42
+ embeddings_prepared: When True, skip all embedding generation and write
43
+ whatever embedding is already on each interaction (real or ``[]``).
44
+ Use on the durable path after ``prepare_interaction_embeddings`` to
45
+ ensure no network I/O occurs inside an open ``commit_scope``.
46
+ """
47
+ raise NotImplementedError
48
+
49
+ def prepare_interaction_embeddings(self, interactions: list[Interaction]) -> None: # noqa: ARG002
50
+ """Pre-populate interaction.embedding for each interaction without writing to storage.
51
+
52
+ Call this before opening a commit_scope to avoid a network round-trip inside
53
+ the transaction. Subclasses that generate embeddings client-side MUST override
54
+ this to call get_embeddings and populate interaction.embedding before the scope
55
+ is opened; backends where the embedding is generated server-side can leave this
56
+ as a no-op.
57
+
58
+ Args:
59
+ interactions: Interactions whose embedding fields will be populated in-place.
60
+ """
61
+ return
62
+
63
+ @abstractmethod
64
+ def delete_user_interaction(self, request: DeleteUserInteractionRequest) -> None:
65
+ raise NotImplementedError
66
+
67
+ @abstractmethod
68
+ def delete_all_interactions_for_user(self, user_id: str) -> None:
69
+ raise NotImplementedError
70
+
71
+ @abstractmethod
72
+ def delete_all_interactions(self) -> None:
73
+ """Delete all interactions across all users."""
74
+ raise NotImplementedError
75
+
76
+ @abstractmethod
77
+ def count_all_interactions(self) -> int:
78
+ """Count total interactions across all users.
79
+
80
+ Returns:
81
+ int: Total number of interactions
82
+ """
83
+ raise NotImplementedError
84
+
85
+ @abstractmethod
86
+ def delete_oldest_interactions(self, count: int) -> int:
87
+ """Delete the oldest N interactions based on created_at timestamp.
88
+
89
+ Args:
90
+ count (int): Number of oldest interactions to delete
91
+
92
+ Returns:
93
+ int: Number of interactions actually deleted
94
+ """
95
+ raise NotImplementedError
@@ -1,22 +1,15 @@
1
1
  from abc import abstractmethod
2
2
 
3
3
  from reflexio.models.api_schema.domain import (
4
- DeleteUserInteractionRequest,
5
4
  DeleteUserProfileRequest,
6
- Interaction,
7
5
  Status,
8
6
  UserProfile,
9
7
  )
10
- from reflexio.models.api_schema.retriever_schema import (
11
- SearchInteractionRequest,
12
- SearchUserProfileRequest,
13
- )
14
8
 
15
9
 
16
- class ProfileMixin:
17
- """Mixin for profile and interaction CRUD methods."""
10
+ class ProfileStoreMixin:
11
+ """Mixin for profile-store CRUD methods."""
18
12
 
19
- # read methods
20
13
  @abstractmethod
21
14
  def get_all_profiles(
22
15
  self,
@@ -32,10 +25,6 @@ class ProfileMixin:
32
25
  ) -> list[UserProfile]:
33
26
  raise NotImplementedError
34
27
 
35
- @abstractmethod
36
- def get_all_interactions(self, limit: int = 100) -> list[Interaction]:
37
- raise NotImplementedError
38
-
39
28
  @abstractmethod
40
29
  def get_user_profile(
41
30
  self,
@@ -48,38 +37,37 @@ class ProfileMixin:
48
37
  profile_time_to_live: str | None = None,
49
38
  start_time: int | None = None,
50
39
  end_time: int | None = None,
40
+ include_expired: bool = False,
51
41
  ) -> list[UserProfile]:
52
- raise NotImplementedError
53
-
54
- @abstractmethod
55
- def get_user_interaction(self, user_id: str) -> list[Interaction]:
56
- raise NotImplementedError
57
-
58
- # create or update methods
59
- @abstractmethod
60
- def add_user_profile(self, user_id: str, user_profiles: list[UserProfile]) -> None:
61
- """Add the user profile for a given user id."""
62
- raise NotImplementedError
63
-
64
- @abstractmethod
65
- def add_user_interaction(self, user_id: str, interaction: Interaction) -> None:
66
- raise NotImplementedError
67
-
68
- @abstractmethod
69
- def add_user_interactions_bulk(
70
- self, user_id: str, interactions: list[Interaction]
71
- ) -> None:
72
- """Add multiple user interactions with batched embedding generation.
42
+ """Return profiles for a user, optionally including expired rows.
73
43
 
74
44
  Args:
75
- user_id: The user ID
76
- interactions: List of interactions to add
45
+ user_id: The user whose profiles to return.
46
+ status_filter: Statuses to include (``None`` element = CURRENT).
47
+ Defaults to ``[None]`` (CURRENT only) when not supplied.
48
+ tags: If provided, only return profiles whose ``tags`` overlap.
49
+ profile_id: If provided, filter to this exact profile id.
50
+ query: Free-text substring match across content, profile_id, user_id.
51
+ source: If provided, filter to this exact source value.
52
+ profile_time_to_live: If provided, filter to this TTL value.
53
+ start_time: If provided, lower bound on last_modified_timestamp.
54
+ end_time: If provided, upper bound on last_modified_timestamp.
55
+ include_expired: When ``False`` (default), rows whose
56
+ ``expiration_timestamp`` is in the past are excluded
57
+ (byte-for-byte prior behaviour — every existing caller is
58
+ unaffected). Pass ``True`` to omit the expiry filter so
59
+ EXPIRED tombstones (``expiration_timestamp < now``) are
60
+ returned. Required by ``clear_user_data`` to reach every
61
+ profile a user owns regardless of TTL state.
62
+
63
+ Returns:
64
+ list[UserProfile]: Matching profiles in unspecified order.
77
65
  """
78
66
  raise NotImplementedError
79
67
 
80
- # delete methods
81
68
  @abstractmethod
82
- def delete_user_interaction(self, request: DeleteUserInteractionRequest) -> None:
69
+ def add_user_profile(self, user_id: str, user_profiles: list[UserProfile]) -> None:
70
+ """Add the user profile for a given user id."""
83
71
  raise NotImplementedError
84
72
 
85
73
  @abstractmethod
@@ -99,10 +87,6 @@ class ProfileMixin:
99
87
  """Replace only the tags of a profile, leaving content and embedding untouched."""
100
88
  raise NotImplementedError
101
89
 
102
- @abstractmethod
103
- def delete_all_interactions_for_user(self, user_id: str) -> None:
104
- raise NotImplementedError
105
-
106
90
  @abstractmethod
107
91
  def delete_all_profiles_for_user(self, user_id: str) -> None:
108
92
  raise NotImplementedError
@@ -112,20 +96,6 @@ class ProfileMixin:
112
96
  """Delete all profiles across all users."""
113
97
  raise NotImplementedError
114
98
 
115
- @abstractmethod
116
- def delete_all_interactions(self) -> None:
117
- """Delete all interactions across all users."""
118
- raise NotImplementedError
119
-
120
- @abstractmethod
121
- def count_all_interactions(self) -> int:
122
- """Count total interactions across all users.
123
-
124
- Returns:
125
- int: Total number of interactions
126
- """
127
- raise NotImplementedError
128
-
129
99
  @abstractmethod
130
100
  def count_all_profiles(self) -> int:
131
101
  """Count total profiles across all users without hydrating rows.
@@ -140,17 +110,27 @@ class ProfileMixin:
140
110
  """
141
111
  raise NotImplementedError
142
112
 
143
- @abstractmethod
144
- def delete_oldest_interactions(self, count: int) -> int:
145
- """Delete the oldest N interactions based on created_at timestamp.
113
+ def count_user_profiles_by_status(
114
+ self, user_ids: list[str], status: Status | None
115
+ ) -> int:
116
+ """Count non-expired profiles for concrete users with the given status.
117
+
118
+ Storage backends may override this with a single query. The default keeps
119
+ the storage contract backward-compatible for out-of-tree backends.
146
120
 
147
121
  Args:
148
- count (int): Number of oldest interactions to delete
122
+ user_ids: Concrete user IDs to include. Empty list returns 0.
123
+ status: Profile status to match; None means CURRENT.
149
124
 
150
125
  Returns:
151
- int: Number of interactions actually deleted
126
+ int: Number of matching profiles
152
127
  """
153
- raise NotImplementedError
128
+ if not user_ids:
129
+ return 0
130
+ return sum(
131
+ len(self.get_user_profile(user_id=user_id, status_filter=[status]))
132
+ for user_id in user_ids
133
+ )
154
134
 
155
135
  @abstractmethod
156
136
  def update_all_profiles_status(
@@ -171,6 +151,24 @@ class ProfileMixin:
171
151
  """
172
152
  raise NotImplementedError
173
153
 
154
+ @abstractmethod
155
+ def expire_active_profiles(self, *, now: int, limit: int = 1000) -> int:
156
+ """Tombstone active profiles whose TTL has elapsed.
157
+
158
+ Selects ``status IS NULL AND expiration_timestamp < now`` and transitions
159
+ each to ``status=EXPIRED, retired_at=now``, emitting a ``status_change``
160
+ lineage event (reason ``ttl-expired``). Returns the number tombstoned.
161
+
162
+ Args:
163
+ now: Current epoch timestamp (seconds). Profiles with
164
+ ``expiration_timestamp < now`` are tombstoned.
165
+ limit: Maximum number of profiles to tombstone in one call (default 1000).
166
+
167
+ Returns:
168
+ int: Number of profiles tombstoned.
169
+ """
170
+ raise NotImplementedError
171
+
174
172
  @abstractmethod
175
173
  def get_profiles_by_ids(
176
174
  self,
@@ -363,21 +361,3 @@ class ProfileMixin:
363
361
  commit-atomic legacy change-log "removed" entry.
364
362
  """
365
363
  raise NotImplementedError
366
-
367
- # Search methods
368
- @abstractmethod
369
- def search_interaction(
370
- self,
371
- search_interaction_request: SearchInteractionRequest,
372
- query_embedding: list[float] | None = None,
373
- ) -> list[Interaction]:
374
- raise NotImplementedError
375
-
376
- @abstractmethod
377
- def search_user_profile(
378
- self,
379
- search_user_profile_request: SearchUserProfileRequest,
380
- status_filter: list[Status | None] | None = None,
381
- query_embedding: list[float] | None = None,
382
- ) -> list[UserProfile]:
383
- raise NotImplementedError
@@ -0,0 +1,32 @@
1
+ from abc import abstractmethod
2
+
3
+ from reflexio.models.api_schema.domain import (
4
+ Interaction,
5
+ Status,
6
+ UserProfile,
7
+ )
8
+ from reflexio.models.api_schema.retriever_schema import (
9
+ SearchInteractionRequest,
10
+ SearchUserProfileRequest,
11
+ )
12
+
13
+
14
+ class ProfileSearchMixin:
15
+ """Mixin for interaction + profile search methods."""
16
+
17
+ @abstractmethod
18
+ def search_interaction(
19
+ self,
20
+ search_interaction_request: SearchInteractionRequest,
21
+ query_embedding: list[float] | None = None,
22
+ ) -> list[Interaction]:
23
+ raise NotImplementedError
24
+
25
+ @abstractmethod
26
+ def search_user_profile(
27
+ self,
28
+ search_user_profile_request: SearchUserProfileRequest,
29
+ status_filter: list[Status | None] | None = None,
30
+ query_embedding: list[float] | None = None,
31
+ ) -> list[UserProfile]:
32
+ raise NotImplementedError
@@ -1,148 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import ABC, abstractmethod
4
- from typing import Literal
5
-
6
- from reflexio.models.api_schema.domain.governance import (
7
- AuditEvent,
8
- PurgeOperation,
9
- PurgeOperationTarget,
10
- SubjectWriteBarrier,
11
- )
12
- from reflexio.models.config_schema import GovernanceRetentionConfig
13
-
14
-
15
- class GovernanceMixin(ABC):
16
- """Mixin for backend-neutral governance storage primitives."""
17
-
18
- @abstractmethod
19
- def append_audit_event(self, event: AuditEvent) -> bool:
20
- raise NotImplementedError
21
-
22
- @abstractmethod
23
- def list_audit_events(
24
- self, subject_ref: str | None = None, *, org_id: str | None = None
25
- ) -> list[AuditEvent]:
26
- raise NotImplementedError
27
-
28
- @abstractmethod
29
- def begin_purge_operation(
30
- self,
31
- purge_id: str,
32
- idempotency_key: str,
33
- operation_type: Literal["user_erasure", "org_purge"],
34
- scope_type: Literal["user", "org"],
35
- subject_ref: str | None,
36
- request_ref: str,
37
- ) -> PurgeOperation:
38
- raise NotImplementedError
39
-
40
- @abstractmethod
41
- def record_purge_target(
42
- self,
43
- purge_id: str,
44
- target_name: str,
45
- phase: str,
46
- status: Literal["pending", "running", "failed", "complete"],
47
- target_ref: str = "",
48
- detail: dict[str, object] | None = None,
49
- deleted_count: int = 0,
50
- error_detail: str | None = None,
51
- ) -> None:
52
- raise NotImplementedError
53
-
54
- @abstractmethod
55
- def list_purge_targets(
56
- self, purge_id: str, phase: str | None = None
57
- ) -> list[PurgeOperationTarget]:
58
- raise NotImplementedError
59
-
60
- @abstractmethod
61
- def purge_targets_prepared(self, purge_id: str) -> bool:
62
- raise NotImplementedError
63
-
64
- @abstractmethod
65
- def prepare_governance_erase_targets(
66
- self,
67
- purge_id: str,
68
- user_id: str,
69
- owned_user_playbook_ids: set[int] | None = None,
70
- ) -> None:
71
- raise NotImplementedError
72
-
73
- @abstractmethod
74
- def hide_governance_agent_playbooks_for_rebuild(self, purge_id: str) -> list[int]:
75
- raise NotImplementedError
76
-
77
- @abstractmethod
78
- def apply_governance_user_data_delete(
79
- self, purge_id: str, user_id: str
80
- ) -> dict[str, int]:
81
- raise NotImplementedError
82
-
83
- @abstractmethod
84
- def apply_governance_agent_playbook_rebuild(
85
- self,
86
- purge_id: str,
87
- agent_playbook_id: int,
88
- remaining_source_windows: list[dict[str, object]],
89
- content: str | None,
90
- trigger: str | None,
91
- rationale: str | None,
92
- blocking_issue: dict[str, object] | None,
93
- expanded_terms: str | None,
94
- tags: list[str] | None,
95
- ) -> None:
96
- raise NotImplementedError
97
-
98
- @abstractmethod
99
- def complete_purge_operation_with_audit(
100
- self, purge_id: str, audit_event: AuditEvent
101
- ) -> PurgeOperation:
102
- raise NotImplementedError
103
-
104
- @abstractmethod
105
- def begin_subject_erasure_barrier(
106
- self, subject_ref: str, purge_id: str
107
- ) -> SubjectWriteBarrier:
108
- raise NotImplementedError
109
-
110
- @abstractmethod
111
- def assert_subject_writable(self, subject_ref: str) -> None:
112
- raise NotImplementedError
113
-
114
- @abstractmethod
115
- def complete_subject_erasure_barrier_after_empty_check(
116
- self, purge_id: str, audit_event: AuditEvent
117
- ) -> PurgeOperation:
118
- raise NotImplementedError
119
-
120
- @abstractmethod
121
- def fail_subject_erasure_barrier(
122
- self,
123
- subject_ref: str,
124
- purge_id: str,
125
- error_code: str,
126
- error_detail: str,
127
- ) -> SubjectWriteBarrier:
128
- raise NotImplementedError
129
-
130
- @abstractmethod
131
- def get_subject_write_barrier(
132
- self, subject_ref: str
133
- ) -> SubjectWriteBarrier | None:
134
- raise NotImplementedError
135
-
136
- @abstractmethod
137
- def fail_purge_operation(
138
- self, purge_id: str, error_code: str, error_detail: str
139
- ) -> PurgeOperation:
140
- raise NotImplementedError
141
-
142
- @abstractmethod
143
- def get_purge_operation(self, purge_id: str) -> PurgeOperation:
144
- raise NotImplementedError
145
-
146
- @abstractmethod
147
- def gc_governance_retention(self, *, config: GovernanceRetentionConfig) -> int:
148
- raise NotImplementedError