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.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +47 -10
- package/bin/claude-smart.js +535 -23
- package/package.json +22 -3
- package/plugin/.claude-plugin/plugin.json +4 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +27 -6
- package/plugin/commands/clear-all.md +1 -0
- package/plugin/commands/dashboard.md +1 -0
- package/plugin/commands/learn.md +1 -0
- package/plugin/commands/restart.md +1 -0
- package/plugin/commands/show.md +1 -0
- package/plugin/dashboard/app/configure/env/page.tsx +40 -14
- package/plugin/dashboard/app/configure/server/page.tsx +51 -1
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
- package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
- package/plugin/dashboard/lib/reflexio-client.ts +16 -0
- package/plugin/dashboard/lib/status.ts +10 -3
- package/plugin/dashboard/lib/types.ts +18 -6
- package/plugin/hooks/codex-hooks.json +0 -1
- package/plugin/opencode/assistant-buffer.ts +108 -0
- package/plugin/opencode/dist/assistant-buffer.js +96 -0
- package/plugin/opencode/dist/internal.js +12 -0
- package/plugin/opencode/dist/payload.js +85 -0
- package/plugin/opencode/dist/server.mjs +232 -0
- package/plugin/opencode/internal.ts +18 -0
- package/plugin/opencode/package.json +3 -0
- package/plugin/opencode/payload.ts +90 -0
- package/plugin/opencode/server.mts +251 -0
- package/plugin/opencode/tsconfig.json +13 -0
- package/plugin/pyproject.toml +25 -4
- package/plugin/scripts/_lib.sh +5 -1
- package/plugin/scripts/backend-service.sh +22 -2
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +1 -1
- package/plugin/scripts/opencode-claude-compat +4 -0
- package/plugin/scripts/opencode-claude-compat.cmd +3 -0
- package/plugin/scripts/opencode-claude-compat.js +225 -0
- package/plugin/scripts/smart-install.sh +17 -9
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +498 -12
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/src/claude_smart/env_config.py +12 -4
- package/plugin/src/claude_smart/events/session_start.py +26 -7
- package/plugin/src/claude_smart/events/stop.py +1 -1
- package/plugin/src/claude_smart/ids.py +1 -1
- package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
- package/plugin/src/claude_smart/runtime.py +7 -1
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +54 -30
- package/plugin/vendor/reflexio/README.md +17 -11
- package/plugin/vendor/reflexio/pyproject.toml +13 -1
- package/plugin/vendor/reflexio/reflexio/README.md +4 -1
- package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +212 -52
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +154 -13
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +246 -10
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
- package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +97 -3
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +104 -7
- package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
- package/plugin/vendor/reflexio/reflexio/server/README.md +58 -33
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +899 -103
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
- package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
- package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +278 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +28 -11
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +47 -16
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +18 -11
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +334 -178
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +31 -22
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -6
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +16 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +644 -197
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +31 -1369
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +40 -57
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +4 -677
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +372 -34
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
- package/scripts/setup-claude-smart.sh +8 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +0 -25
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
- /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
- /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}"
|
|
File without changes
|
|
@@ -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
|