claude-smart 0.2.45 → 0.2.47

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 (353) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +47 -10
  3. package/bin/claude-smart.js +535 -23
  4. package/package.json +22 -3
  5. package/plugin/.claude-plugin/plugin.json +4 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +27 -6
  8. package/plugin/commands/clear-all.md +1 -0
  9. package/plugin/commands/dashboard.md +1 -0
  10. package/plugin/commands/learn.md +1 -0
  11. package/plugin/commands/restart.md +1 -0
  12. package/plugin/commands/show.md +1 -0
  13. package/plugin/dashboard/app/configure/env/page.tsx +40 -14
  14. package/plugin/dashboard/app/configure/server/page.tsx +51 -1
  15. package/plugin/dashboard/app/layout.tsx +20 -0
  16. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  17. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  18. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  19. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  20. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  21. package/plugin/dashboard/lib/status.ts +10 -3
  22. package/plugin/dashboard/lib/types.ts +18 -6
  23. package/plugin/hooks/codex-hooks.json +0 -1
  24. package/plugin/opencode/assistant-buffer.ts +108 -0
  25. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  26. package/plugin/opencode/dist/internal.js +12 -0
  27. package/plugin/opencode/dist/payload.js +85 -0
  28. package/plugin/opencode/dist/server.mjs +232 -0
  29. package/plugin/opencode/internal.ts +18 -0
  30. package/plugin/opencode/package.json +3 -0
  31. package/plugin/opencode/payload.ts +90 -0
  32. package/plugin/opencode/server.mts +251 -0
  33. package/plugin/opencode/tsconfig.json +13 -0
  34. package/plugin/pyproject.toml +25 -4
  35. package/plugin/scripts/_lib.sh +5 -1
  36. package/plugin/scripts/backend-service.sh +22 -2
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +1 -1
  39. package/plugin/scripts/opencode-claude-compat +4 -0
  40. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  41. package/plugin/scripts/opencode-claude-compat.js +225 -0
  42. package/plugin/scripts/smart-install.sh +17 -9
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +498 -12
  45. package/plugin/src/claude_smart/context_format.py +9 -9
  46. package/plugin/src/claude_smart/cs_cite.py +66 -19
  47. package/plugin/src/claude_smart/env_config.py +12 -4
  48. package/plugin/src/claude_smart/events/session_start.py +26 -7
  49. package/plugin/src/claude_smart/events/stop.py +1 -1
  50. package/plugin/src/claude_smart/ids.py +1 -1
  51. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  52. package/plugin/src/claude_smart/runtime.py +7 -1
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +54 -30
  55. package/plugin/vendor/reflexio/README.md +17 -11
  56. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  57. package/plugin/vendor/reflexio/reflexio/README.md +4 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  59. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  60. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  64. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +212 -52
  65. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  66. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  67. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  68. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  69. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  70. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  71. package/plugin/vendor/reflexio/reflexio/client/client.py +154 -13
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +246 -10
  146. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  147. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  148. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
  149. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  150. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
  151. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  152. package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
  153. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  158. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  159. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  160. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  161. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +97 -3
  162. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  163. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  164. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +104 -7
  165. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  166. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  167. package/plugin/vendor/reflexio/reflexio/server/README.md +58 -33
  168. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  169. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  170. package/plugin/vendor/reflexio/reflexio/server/api.py +899 -103
  171. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  172. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
  173. package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
  174. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  175. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  176. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  177. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  178. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  179. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  180. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  181. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  182. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  183. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  185. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  186. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  187. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  188. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  189. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  198. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +278 -0
  199. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  200. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  201. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  202. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  203. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  204. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  205. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  206. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  207. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  208. package/plugin/vendor/reflexio/reflexio/server/services/README.md +28 -11
  209. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  212. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  215. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  216. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  217. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  218. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  219. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  220. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  221. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  222. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  225. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  226. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  227. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  229. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  230. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  231. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +47 -16
  232. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  233. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  234. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -0
  238. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +18 -11
  241. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  242. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  243. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +334 -178
  244. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  245. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  246. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  248. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  249. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  250. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  252. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  255. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +31 -22
  256. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  257. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  258. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  259. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  261. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  262. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  263. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -6
  264. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  265. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  266. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +31 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +16 -2
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +644 -197
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  277. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +31 -1369
  278. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
  279. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
  280. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  281. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  282. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  283. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
  289. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  290. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +40 -57
  292. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +4 -677
  296. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
  297. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +372 -34
  311. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  312. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  313. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  314. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  315. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  316. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  317. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  318. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  319. package/scripts/setup-claude-smart.sh +8 -3
  320. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  321. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  322. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  323. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  324. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  325. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  326. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  327. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  328. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  329. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  330. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  331. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  332. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  333. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  334. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  335. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  336. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  337. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  338. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  339. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  340. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  341. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  342. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  343. package/plugin/vendor/reflexio/reflexio/server/_auth.py +0 -25
  344. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  345. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  346. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  347. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  348. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  349. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  350. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  351. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  352. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  353. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -0,0 +1,378 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import suppress
4
+ from typing import Any, Literal, TypedDict
5
+
6
+ from reflexio.models.api_schema.domain.governance import (
7
+ AuditEvent,
8
+ PurgeOperationTarget,
9
+ SubjectWriteBarrier,
10
+ UserEraseResult,
11
+ UserExportResult,
12
+ )
13
+ from reflexio.server.services.governance.config import (
14
+ get_governance_ref_secret,
15
+ governance_request_ref,
16
+ governance_subject_ref,
17
+ )
18
+ from reflexio.server.services.governance.subject_refs import stable_id
19
+
20
+ _DELETE_TARGET_NAME_TO_RESULT_KEY = {
21
+ "interaction": "interactions",
22
+ "user_playbook": "user_playbooks",
23
+ "profile": "profiles",
24
+ "request": "requests",
25
+ "agent_success_evaluation_result": "agent_success_evaluation_results",
26
+ "profile_purge": "purged_profiles",
27
+ "user_playbook_purge": "purged_user_playbooks",
28
+ }
29
+ _REQUIRED_DELETE_TARGET_NAMES = tuple(_DELETE_TARGET_NAME_TO_RESULT_KEY)
30
+ _USER_PLAYBOOK_PAGE_SIZE = 1000
31
+
32
+
33
+ class GovernanceActorContext(TypedDict):
34
+ actor_type: Literal["api_token", "jwt", "system"]
35
+ actor_ref: str | None
36
+
37
+
38
+ class GovernanceService:
39
+ def __init__(self, *, storage: Any, org_id: str, ref_secret: str) -> None:
40
+ self.storage = storage
41
+ self.org_id = org_id
42
+ self.ref_secret = ref_secret
43
+
44
+ def export_user(
45
+ self,
46
+ *,
47
+ user_id: str,
48
+ request_id: str,
49
+ actor_context: GovernanceActorContext | None = None,
50
+ ) -> UserExportResult:
51
+ self._assert_storage_ref_secret_matches()
52
+ subref = governance_subject_ref(self.org_id, user_id, self.ref_secret)
53
+ reqref = governance_request_ref(self.org_id, request_id, self.ref_secret)
54
+ export_id = stable_id("export", f"{self.org_id}:export:{subref}:{reqref}")
55
+ actor_type = actor_context["actor_type"] if actor_context else "system"
56
+ actor_ref = actor_context["actor_ref"] if actor_context else None
57
+ requests, sessions = self._load_user_requests_and_sessions(user_id)
58
+ bundle: dict[str, Any] = {
59
+ "profiles": [
60
+ profile.model_dump()
61
+ for profile in self.storage.get_user_profile(user_id)
62
+ ],
63
+ "interactions": [
64
+ interaction.model_dump()
65
+ for interaction in self.storage.get_user_interaction(user_id)
66
+ ],
67
+ "requests": [request.model_dump() for request in requests],
68
+ "sessions": sessions,
69
+ "user_playbooks": [
70
+ playbook.model_dump() for playbook in self._iter_user_playbooks(user_id)
71
+ ],
72
+ }
73
+ self.storage.append_audit_event(
74
+ AuditEvent(
75
+ org_id=self.org_id,
76
+ actor_type=actor_type,
77
+ actor_ref=actor_ref,
78
+ operation="EXPORT",
79
+ entity_type="request",
80
+ subject_ref=subref,
81
+ request_ref=reqref,
82
+ idempotency_key=export_id,
83
+ detail={"count": sum(len(items) for items in bundle.values())},
84
+ )
85
+ )
86
+ return UserExportResult(subject_ref=subref, export_id=export_id, bundle=bundle)
87
+
88
+ def erase_user(
89
+ self,
90
+ *,
91
+ user_id: str,
92
+ request_id: str,
93
+ actor_context: GovernanceActorContext | None = None,
94
+ ) -> UserEraseResult:
95
+ self._assert_storage_ref_secret_matches()
96
+ subref = governance_subject_ref(self.org_id, user_id, self.ref_secret)
97
+ reqref = governance_request_ref(self.org_id, request_id, self.ref_secret)
98
+ actor_type = actor_context["actor_type"] if actor_context else "system"
99
+ actor_ref = actor_context["actor_ref"] if actor_context else None
100
+ idempotency_key = stable_id(
101
+ "idem",
102
+ f"{self.org_id}:user_erasure:{subref}:{reqref}",
103
+ )
104
+ purge_id = stable_id("purge", idempotency_key)
105
+ purge = self.storage.begin_purge_operation(
106
+ purge_id=purge_id,
107
+ idempotency_key=idempotency_key,
108
+ operation_type="user_erasure",
109
+ scope_type="user",
110
+ subject_ref=subref,
111
+ request_ref=reqref,
112
+ )
113
+ if purge.status == "complete":
114
+ barrier = self._completed_barrier_for_retry(
115
+ subject_ref=subref, purge_id=purge_id
116
+ )
117
+ if barrier.status != "erased":
118
+ raise ValueError(
119
+ "Completed purge retry requires an erased subject barrier"
120
+ )
121
+ return UserEraseResult(
122
+ subject_ref=subref,
123
+ purge_id=purge_id,
124
+ status="complete",
125
+ deleted_counts=self._deleted_counts_from_targets(purge_id),
126
+ rebuilt_agent_playbook_ids=(
127
+ self._rebuilt_agent_playbook_ids_from_targets(purge_id)
128
+ ),
129
+ )
130
+ try:
131
+ self.storage.begin_subject_erasure_barrier(subref, purge_id)
132
+ if not self.storage.purge_targets_prepared(purge_id):
133
+ self.storage.prepare_governance_erase_targets(
134
+ purge_id,
135
+ user_id,
136
+ )
137
+
138
+ if not self._delete_targets_complete(purge_id):
139
+ self.storage.apply_governance_user_data_delete(purge_id, user_id)
140
+ deleted_counts = self._deleted_counts_from_targets(purge_id)
141
+
142
+ rebuilt_agent_playbook_ids: list[int] = []
143
+ completed = self.storage.complete_subject_erasure_barrier_after_empty_check(
144
+ purge_id,
145
+ AuditEvent(
146
+ org_id=self.org_id,
147
+ actor_type=actor_type,
148
+ actor_ref=actor_ref,
149
+ operation="ERASE",
150
+ entity_type="request",
151
+ subject_ref=subref,
152
+ request_ref=reqref,
153
+ idempotency_key=purge_id,
154
+ detail={
155
+ "deleted_counts": deleted_counts,
156
+ "rebuilt_agent_playbook_ids": rebuilt_agent_playbook_ids,
157
+ },
158
+ ),
159
+ )
160
+ except Exception as exc:
161
+ with suppress(Exception):
162
+ self.storage.fail_subject_erasure_barrier(
163
+ subref,
164
+ purge_id,
165
+ error_code="governance_erase_failed",
166
+ error_detail=type(exc).__name__,
167
+ )
168
+ with suppress(Exception):
169
+ self.storage.fail_purge_operation(
170
+ purge_id,
171
+ error_code="governance_erase_failed",
172
+ error_detail=type(exc).__name__,
173
+ )
174
+ raise
175
+ return UserEraseResult(
176
+ subject_ref=subref,
177
+ purge_id=purge_id,
178
+ status=completed.status,
179
+ deleted_counts=deleted_counts,
180
+ rebuilt_agent_playbook_ids=rebuilt_agent_playbook_ids,
181
+ )
182
+
183
+ def _assert_storage_ref_secret_matches(self) -> None:
184
+ storage_secret = get_governance_ref_secret()
185
+ if storage_secret != self.ref_secret:
186
+ raise RuntimeError(
187
+ "GovernanceService ref_secret must match REFLEXIO_GOVERNANCE_REF_SECRET "
188
+ "for governance operations"
189
+ )
190
+
191
+ def _completed_barrier_for_retry(
192
+ self, *, subject_ref: str, purge_id: str
193
+ ) -> SubjectWriteBarrier:
194
+ barrier = self.storage.get_subject_write_barrier(subject_ref)
195
+ if barrier is None or barrier.purge_id != purge_id:
196
+ raise ValueError(
197
+ "Completed purge retry requires the matching subject barrier"
198
+ )
199
+ return barrier
200
+
201
+ def _load_user_requests_and_sessions(
202
+ self, user_id: str
203
+ ) -> tuple[list[Any], list[dict[str, Any]]]:
204
+ requests: list[Any] = []
205
+ sessions_by_id: dict[str, list[str]] = {}
206
+ offset = 0
207
+ page_size = 1000
208
+
209
+ while True:
210
+ grouped_sessions = self.storage.get_sessions(
211
+ user_id=user_id,
212
+ top_k=page_size,
213
+ offset=offset,
214
+ )
215
+ returned_rows = 0
216
+ for session_id, rows in grouped_sessions.items():
217
+ returned_rows += len(rows)
218
+ request_ids = sessions_by_id.setdefault(session_id, [])
219
+ for row in rows:
220
+ if row.request is None:
221
+ continue
222
+ requests.append(row.request)
223
+ request_ids.append(row.request.request_id)
224
+ if returned_rows < page_size:
225
+ break
226
+ offset += page_size
227
+
228
+ sessions = [
229
+ {"session_id": session_id, "request_ids": request_ids}
230
+ for session_id, request_ids in sessions_by_id.items()
231
+ ]
232
+ return requests, sessions
233
+
234
+ def _iter_user_playbooks(self, user_id: str) -> list[Any]:
235
+ playbooks: list[Any] = []
236
+ offset = 0
237
+ while True:
238
+ page = self.storage.get_user_playbooks(
239
+ user_id=user_id,
240
+ limit=_USER_PLAYBOOK_PAGE_SIZE,
241
+ offset=offset,
242
+ )
243
+ playbooks.extend(page)
244
+ if len(page) < _USER_PLAYBOOK_PAGE_SIZE:
245
+ break
246
+ offset += _USER_PLAYBOOK_PAGE_SIZE
247
+ return playbooks
248
+
249
+ def _delete_targets_complete(self, purge_id: str) -> bool:
250
+ delete_targets = {
251
+ target.target_name: target
252
+ for target in self.storage.list_purge_targets(purge_id, phase="delete")
253
+ }
254
+ return all(
255
+ delete_targets.get(target_name) is not None
256
+ and delete_targets[target_name].status == "complete"
257
+ for target_name in _REQUIRED_DELETE_TARGET_NAMES
258
+ )
259
+
260
+ def _deleted_counts_from_targets(self, purge_id: str) -> dict[str, int]:
261
+ counts: dict[str, int] = {}
262
+ for target in self.storage.list_purge_targets(purge_id, phase="delete"):
263
+ result_key = _DELETE_TARGET_NAME_TO_RESULT_KEY.get(target.target_name)
264
+ if result_key is None:
265
+ continue
266
+ counts[result_key] = int(target.deleted_count)
267
+ return counts
268
+
269
+ def _rebuilt_agent_playbook_ids_from_targets(self, purge_id: str) -> list[int]:
270
+ return [
271
+ int(target.target_ref)
272
+ for target in self.storage.list_purge_targets(
273
+ purge_id,
274
+ phase="rebuild_without_erased_sources",
275
+ )
276
+ if (
277
+ target.target_name == "agent_playbook"
278
+ and target.target_ref
279
+ and target.status == "complete"
280
+ )
281
+ ]
282
+
283
+ def _rebuild_agent_playbooks(self, purge_id: str) -> list[int]:
284
+ rebuilt_ids: list[int] = []
285
+ for target in self.storage.list_purge_targets(
286
+ purge_id,
287
+ phase="rebuild_without_erased_sources",
288
+ ):
289
+ if target.target_name != "agent_playbook" or not target.target_ref:
290
+ continue
291
+ agent_playbook_id = int(target.target_ref)
292
+ if target.status == "complete":
293
+ rebuilt_ids.append(agent_playbook_id)
294
+ continue
295
+ remaining_source_windows = self._remaining_source_windows(target)
296
+ rebuild_fields = self._build_rebuilt_agent_playbook_fields(
297
+ remaining_source_windows
298
+ )
299
+ self.storage.apply_governance_agent_playbook_rebuild(
300
+ purge_id=purge_id,
301
+ agent_playbook_id=agent_playbook_id,
302
+ remaining_source_windows=remaining_source_windows,
303
+ content=rebuild_fields["content"],
304
+ trigger=rebuild_fields["trigger"],
305
+ rationale=rebuild_fields["rationale"],
306
+ blocking_issue=rebuild_fields["blocking_issue"],
307
+ expanded_terms=rebuild_fields["expanded_terms"],
308
+ tags=rebuild_fields["tags"],
309
+ )
310
+ rebuilt_ids.append(agent_playbook_id)
311
+ return rebuilt_ids
312
+
313
+ def _remaining_source_windows(
314
+ self,
315
+ target: PurgeOperationTarget,
316
+ ) -> list[dict[str, object]]:
317
+ detail = target.detail or {}
318
+ remaining = detail.get("remaining_source_windows", [])
319
+ if not isinstance(remaining, list):
320
+ raise ValueError("remaining_source_windows must be a list")
321
+ return remaining
322
+
323
+ def _build_rebuilt_agent_playbook_fields(
324
+ self,
325
+ remaining_source_windows: list[dict[str, object]],
326
+ ) -> dict[str, Any]:
327
+ user_playbook_ids: list[int] = []
328
+ for window in remaining_source_windows:
329
+ raw_user_playbook_id = window.get("user_playbook_id")
330
+ if isinstance(raw_user_playbook_id, int):
331
+ user_playbook_ids.append(raw_user_playbook_id)
332
+ playbooks_by_id = {
333
+ playbook.user_playbook_id: playbook
334
+ for playbook in self.storage.get_user_playbooks_by_ids_any_user(
335
+ user_playbook_ids
336
+ )
337
+ if playbook.user_playbook_id
338
+ }
339
+ remaining_playbooks = [
340
+ playbooks_by_id[user_playbook_id]
341
+ for user_playbook_id in user_playbook_ids
342
+ if user_playbook_id in playbooks_by_id
343
+ ]
344
+ return {
345
+ "content": self._join_non_empty_strings(
346
+ playbook.content for playbook in remaining_playbooks
347
+ ),
348
+ "trigger": self._join_non_empty_strings(
349
+ playbook.trigger for playbook in remaining_playbooks
350
+ ),
351
+ "rationale": self._join_non_empty_strings(
352
+ playbook.rationale for playbook in remaining_playbooks
353
+ ),
354
+ "blocking_issue": next(
355
+ (
356
+ playbook.blocking_issue.model_dump()
357
+ for playbook in remaining_playbooks
358
+ if playbook.blocking_issue is not None
359
+ ),
360
+ None,
361
+ ),
362
+ "expanded_terms": self._join_non_empty_strings(
363
+ playbook.expanded_terms for playbook in remaining_playbooks
364
+ ),
365
+ "tags": self._merge_tags(remaining_playbooks),
366
+ }
367
+
368
+ def _join_non_empty_strings(self, values: Any) -> str | None:
369
+ joined = "\n".join(value for value in values if value)
370
+ return joined or None
371
+
372
+ def _merge_tags(self, playbooks: list[Any]) -> list[str] | None:
373
+ merged_tags: list[str] = []
374
+ for playbook in playbooks:
375
+ for tag in playbook.tags or []:
376
+ if tag not in merged_tags:
377
+ merged_tags.append(tag)
378
+ return merged_tags or None
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import hmac
5
+
6
+
7
+ def _hmac_ref(prefix: str, raw: str, secret: str) -> str:
8
+ if not raw:
9
+ raise ValueError("raw value must be non-empty")
10
+ if not secret:
11
+ raise ValueError("secret must be non-empty")
12
+ digest = hmac.new(secret.encode(), raw.encode(), hashlib.sha256).hexdigest()[:32]
13
+ return f"{prefix}_v1_{digest}"
14
+
15
+
16
+ def subject_ref(raw: str, secret: str) -> str:
17
+ return _hmac_ref("subref", raw, secret)
18
+
19
+
20
+ def actor_ref(raw: str, secret: str) -> str:
21
+ return _hmac_ref("actref", raw, secret)
22
+
23
+
24
+ def request_ref(raw: str, secret: str) -> str:
25
+ return _hmac_ref("reqref", raw, secret)
26
+
27
+
28
+ def stable_id(prefix: str, material: str) -> str:
29
+ if not prefix:
30
+ raise ValueError("prefix must be non-empty")
31
+ if not material:
32
+ raise ValueError("material must be non-empty")
33
+ digest = hashlib.sha256(material.encode()).hexdigest()[:32]
34
+ return f"{prefix}_{digest}"
@@ -0,0 +1,219 @@
1
+ """Process-local scheduler for lineage tombstone garbage collection.
2
+
3
+ Startup is gated on ``lineage_gc.enabled`` (default on). Each tick evaluates
4
+ every org independently and hard-deletes expired tombstones per that org's
5
+ ``lineage_gc`` config. One org's failure never stalls the loop; errors are
6
+ captured as Sentry anomalies and the loop continues to the next org.
7
+
8
+ Governance-retention reclamation is a premium concern handled by the enterprise
9
+ GovernanceRetentionCapability (reflexio_ext), not here.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import threading
16
+ import time
17
+ from collections.abc import Callable
18
+
19
+ from reflexio.server.api_endpoints.request_context import RequestContext
20
+ from reflexio.server.tracing import capture_anomaly
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ _DEFAULT_POLL_INTERVAL_SECONDS = 86400
25
+ _MIN_POLL_SECONDS = 1
26
+
27
+ # Window-misconfiguration tripwire: if a single tick deletes more than this
28
+ # many tombstones for one org, something is likely wrong with the grace window.
29
+ _HIGH_VOLUME_THRESHOLD = 1000
30
+
31
+ _ENTITY_TYPES = ("user_playbook", "agent_playbook", "profile")
32
+
33
+
34
+ class LineageGCScheduler:
35
+ """Polling daemon that hard-deletes expired tombstones per org."""
36
+
37
+ def __init__(
38
+ self,
39
+ *,
40
+ request_context_factory: Callable[[str], RequestContext],
41
+ bootstrap_org_id: str,
42
+ ) -> None:
43
+ self.request_context_factory = request_context_factory
44
+ self.bootstrap_org_id = bootstrap_org_id
45
+ self._stop_event = threading.Event()
46
+ self._thread: threading.Thread | None = None
47
+
48
+ def start(self) -> None:
49
+ """Start the daemon thread (idempotent if already running)."""
50
+ if self._thread is not None and self._thread.is_alive():
51
+ return
52
+ self._stop_event.clear()
53
+ self._thread = threading.Thread(
54
+ target=self._run_loop,
55
+ name="reflexio-lineage-gc-scheduler",
56
+ daemon=True,
57
+ )
58
+ self._thread.start()
59
+ logger.info("event=lineage_gc_scheduler_started")
60
+
61
+ def stop(self, *, timeout_seconds: float = 5.0) -> None:
62
+ """Signal the daemon to stop and wait for it to finish."""
63
+ self._stop_event.set()
64
+ if self._thread is not None:
65
+ self._thread.join(timeout=timeout_seconds)
66
+ self._thread = None
67
+ logger.info("event=lineage_gc_scheduler_stopped")
68
+
69
+ def _discover_org_ids(self, bootstrap_ctx: RequestContext) -> list[str]:
70
+ """Return every known org, always including the bootstrap org."""
71
+ storage = getattr(bootstrap_ctx, "storage", None)
72
+ org_ids: list[str] = []
73
+ if storage is not None:
74
+ try:
75
+ org_ids = storage.list_org_ids()
76
+ except NotImplementedError:
77
+ logger.warning(
78
+ "event=lineage_gc_list_org_ids_not_implemented "
79
+ "backend=%s bootstrap_org_id=%s — GC will only process bootstrap org",
80
+ type(storage).__name__,
81
+ bootstrap_ctx.org_id,
82
+ )
83
+ org_ids = []
84
+ if bootstrap_ctx.org_id not in org_ids:
85
+ org_ids = [bootstrap_ctx.org_id, *org_ids]
86
+ return org_ids
87
+
88
+ def _gc_tick(self, org_ids: list[str]) -> None:
89
+ """Run one GC pass across the given org IDs.
90
+
91
+ Factored out of ``_run_loop`` so tests can exercise it without threads.
92
+
93
+ Args:
94
+ org_ids (list[str]): Org IDs to process in this tick.
95
+ """
96
+ for org_id in org_ids:
97
+ if self._stop_event.is_set():
98
+ break
99
+ try:
100
+ ctx = self.request_context_factory(org_id)
101
+ if ctx.storage is None:
102
+ continue
103
+ cfg = ctx.configurator.get_config()
104
+ if not cfg.lineage_gc.enabled:
105
+ continue
106
+ older_than_epoch = (
107
+ int(time.time())
108
+ - cfg.lineage_gc.tombstone_grace_window_days * 86400
109
+ )
110
+ tombstone_deleted = 0
111
+ for entity_type in _ENTITY_TYPES:
112
+ tombstone_deleted += ctx.storage.gc_expired_tombstones(
113
+ entity_type=entity_type,
114
+ older_than_epoch=older_than_epoch,
115
+ )
116
+ if tombstone_deleted:
117
+ logger.info(
118
+ "event=lineage_gc_tick org_id=%s tombstone_deleted=%d",
119
+ org_id,
120
+ tombstone_deleted,
121
+ )
122
+ if tombstone_deleted > _HIGH_VOLUME_THRESHOLD:
123
+ capture_anomaly(
124
+ "lineage.gc.high_volume",
125
+ org_id=org_id,
126
+ count=tombstone_deleted,
127
+ )
128
+ except Exception:
129
+ capture_anomaly("lineage.gc.run_failed", org_id=org_id)
130
+ logger.exception("event=lineage_gc_org_failed org_id=%s", org_id)
131
+
132
+ def _run_loop(self) -> None:
133
+ while not self._stop_event.is_set():
134
+ poll_interval = _DEFAULT_POLL_INTERVAL_SECONDS
135
+ try:
136
+ bootstrap_ctx = self.request_context_factory(self.bootstrap_org_id)
137
+ cfg = bootstrap_ctx.configurator.get_config()
138
+ poll_interval = cfg.lineage_gc.poll_interval_seconds
139
+ org_ids = self._discover_org_ids(bootstrap_ctx)
140
+ self._gc_tick(org_ids)
141
+ except Exception:
142
+ logger.exception("event=lineage_gc_scheduler_tick_failed")
143
+ self._stop_event.wait(max(poll_interval, _MIN_POLL_SECONDS))
144
+
145
+
146
+ def maybe_start_lineage_gc(
147
+ request_context_factory: Callable[[str], RequestContext],
148
+ *,
149
+ bootstrap_org_id: str,
150
+ ) -> LineageGCScheduler | None:
151
+ """Start the scheduler only when bootstrap config enables tombstone GC.
152
+
153
+ Startup requires bootstrap-org config to enable tombstone GC via
154
+ ``lineage_gc.enabled``. Tombstone-GC enablement criteria (must
155
+ ALL hold before enabling for a production org):
156
+
157
+ 1. **Mechanism**: GC ages tombstones by ``retired_at`` (the INTEGER epoch
158
+ written at every tombstone write-path). Rows with ``retired_at = NULL``
159
+ (created before the column was added) are never eligible and are retained.
160
+ 2. **Grace window**: 90 days is the vetted default (``tombstone_grace_window_days``).
161
+ This is a per-deployment policy knob — do not shorten without reviewing
162
+ PII-lifetime obligations (GDPR Art. 5(1)(e)) and audit-depth requirements.
163
+ 3. **B2↔B3 timing gate**: enable per-org only once the grace window is ≥ the
164
+ reconstruction read-back horizon used by B3 changelog replay, OR once B3 is
165
+ fully shipped and the horizon is confirmed. Enabling before this point risks
166
+ GC'ing tombstones the B3 replay still needs.
167
+ 4. **DPO sign-off**: obtain sign-off on the PII-lifetime and audit-depth
168
+ implications before enabling in any deployment that processes personal data.
169
+
170
+ Args:
171
+ request_context_factory: Builds an org-scoped :class:`RequestContext`.
172
+ bootstrap_org_id: Org used to read config and seed cross-org discovery.
173
+
174
+ Returns:
175
+ LineageGCScheduler: The started scheduler, or ``None`` if not enabled.
176
+ """
177
+ try:
178
+ ctx = request_context_factory(bootstrap_org_id)
179
+ cfg = ctx.configurator.get_config()
180
+
181
+ # Dead-knob warning: audit-event retention is an ENTERPRISE-only feature
182
+ # (handled by reflexio_ext GovernanceRetentionCapability). In an OSS-only
183
+ # deployment the knob is accepted but does nothing — warn loudly. Detected
184
+ # via the configurator class: enterprise swaps in EnterpriseConfigurator at
185
+ # construction, so the OSS DefaultConfigurator means "no enterprise here".
186
+ from reflexio.server.services.configurator.configurator import ( # noqa: PLC0415
187
+ DefaultConfigurator,
188
+ get_configurator_class,
189
+ )
190
+
191
+ gr = getattr(cfg, "governance_retention", None)
192
+ if (
193
+ gr is not None
194
+ and getattr(gr, "audit_events_retention_enabled", False)
195
+ and get_configurator_class() is DefaultConfigurator
196
+ ):
197
+ logger.warning(
198
+ "event=governance_retention_knob_ignored "
199
+ "audit_events_retention_enabled=True but this is an OSS-only "
200
+ "deployment — audit-event retention is an enterprise-only feature "
201
+ "and will NOT run here."
202
+ )
203
+
204
+ if not cfg.lineage_gc.enabled:
205
+ return None
206
+ except Exception as exc:
207
+ logger.warning(
208
+ "event=lineage_gc_scheduler_start_skipped error_type=%s error=%s",
209
+ type(exc).__name__,
210
+ exc,
211
+ )
212
+ return None
213
+
214
+ scheduler = LineageGCScheduler(
215
+ request_context_factory=request_context_factory,
216
+ bootstrap_org_id=bootstrap_org_id,
217
+ )
218
+ scheduler.start()
219
+ return scheduler