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,630 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sqlite3
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from reflexio.models.api_schema.domain.entities import LineageContext, LineageEvent
|
|
9
|
+
from reflexio.models.api_schema.domain.enums import Status
|
|
10
|
+
from reflexio.server.tracing import capture_anomaly
|
|
11
|
+
|
|
12
|
+
from ._base import _epoch_now
|
|
13
|
+
|
|
14
|
+
EntityType = Literal["user_playbook", "agent_playbook", "profile"]
|
|
15
|
+
|
|
16
|
+
# GC-eligible statuses — rows with these statuses may be hard-deleted by TTL GC.
|
|
17
|
+
# Also used as the merge guard: a source that already carries any of these
|
|
18
|
+
# statuses is skipped (no re-tombstone, no clock reset).
|
|
19
|
+
_GC_ELIGIBLE_STATUSES: frozenset[str] = frozenset(
|
|
20
|
+
{Status.MERGED.value, Status.SUPERSEDED.value, Status.ARCHIVED.value}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Mapping from entity_type string to (table_name, primary_key_column).
|
|
24
|
+
_TABLE: dict[str, tuple[str, str]] = {
|
|
25
|
+
"user_playbook": ("user_playbooks", "user_playbook_id"),
|
|
26
|
+
"agent_playbook": ("agent_playbooks", "agent_playbook_id"),
|
|
27
|
+
"profile": ("profiles", "profile_id"),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Error message used by merge_records and supersede_record guards.
|
|
31
|
+
# Shared here so tests can reference this exact string without hardcoding.
|
|
32
|
+
_EMPTY_REQUEST_ID_MSG = "request_id must be non-empty"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _resolve_table(entity_type: str) -> tuple[str, str]:
|
|
36
|
+
"""Map an entity_type to its (table, primary_key), raising on bad input."""
|
|
37
|
+
table = _TABLE.get(entity_type)
|
|
38
|
+
if table is None:
|
|
39
|
+
raise ValueError(f"unknown entity_type: {entity_type!r}")
|
|
40
|
+
return table
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _append_event_stmt(
|
|
44
|
+
conn: sqlite3.Connection,
|
|
45
|
+
*,
|
|
46
|
+
org_id: str,
|
|
47
|
+
entity_type: str,
|
|
48
|
+
entity_id: str,
|
|
49
|
+
op: str,
|
|
50
|
+
prov: str,
|
|
51
|
+
source_ids: list[str],
|
|
52
|
+
actor: str,
|
|
53
|
+
request_id: str,
|
|
54
|
+
reason: str,
|
|
55
|
+
created_at: int | None = None,
|
|
56
|
+
from_status: str | None = None,
|
|
57
|
+
to_status: str | None = None,
|
|
58
|
+
status_namespace: str | None = None,
|
|
59
|
+
) -> sqlite3.Cursor:
|
|
60
|
+
"""Insert a lineage event row; no-ops on (org_id, entity_type, entity_id, op, request_id) duplicate.
|
|
61
|
+
|
|
62
|
+
Returns the cursor so callers can inspect ``rowcount``/``lastrowid``.
|
|
63
|
+
"""
|
|
64
|
+
return conn.execute(
|
|
65
|
+
"INSERT OR IGNORE INTO lineage_event "
|
|
66
|
+
"(org_id, entity_type, entity_id, op, prov_relation, source_ids, "
|
|
67
|
+
"actor, request_id, reason, created_at, "
|
|
68
|
+
"from_status, to_status, status_namespace) "
|
|
69
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
70
|
+
(
|
|
71
|
+
org_id,
|
|
72
|
+
entity_type,
|
|
73
|
+
entity_id,
|
|
74
|
+
op,
|
|
75
|
+
prov,
|
|
76
|
+
json.dumps(source_ids),
|
|
77
|
+
actor,
|
|
78
|
+
request_id,
|
|
79
|
+
reason,
|
|
80
|
+
created_at if created_at is not None else int(time.time()),
|
|
81
|
+
from_status,
|
|
82
|
+
to_status,
|
|
83
|
+
status_namespace,
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Per-entity purge SQL: blank every PII/content column.
|
|
89
|
+
# No ``content != ''`` guard — a row with content='' but other PII still populated
|
|
90
|
+
# (user_id, embedding, tags, …) would be skipped by that guard, leaving its PII live.
|
|
91
|
+
# Blanking already-blank columns is an idempotent no-op in SQLite (rowcount=1 either
|
|
92
|
+
# way because the row matched); event idempotency is guaranteed by the deterministic
|
|
93
|
+
# request_id + INSERT OR IGNORE on (org,entity_type,entity_id,op,request_id).
|
|
94
|
+
_PROFILE_PURGE_SQL = (
|
|
95
|
+
"UPDATE profiles SET "
|
|
96
|
+
"content='', user_id='', generated_from_request_id='', source='', "
|
|
97
|
+
"embedding=NULL, extractor_names=NULL, expanded_terms=NULL, tags=NULL, "
|
|
98
|
+
"custom_features=NULL, notes=NULL, source_span=NULL, reader_angle=NULL, "
|
|
99
|
+
"governance_subject_ref=NULL "
|
|
100
|
+
"WHERE profile_id=?"
|
|
101
|
+
)
|
|
102
|
+
_USER_PLAYBOOK_PURGE_SQL = (
|
|
103
|
+
"UPDATE user_playbooks SET "
|
|
104
|
+
"content='', user_id=NULL, request_id='', source=NULL, "
|
|
105
|
+
"trigger=NULL, rationale=NULL, blocking_issue=NULL, "
|
|
106
|
+
"source_interaction_ids=NULL, embedding=NULL, expanded_terms=NULL, "
|
|
107
|
+
"tags=NULL, source_span=NULL, notes=NULL, reader_angle=NULL, "
|
|
108
|
+
"governance_subject_ref=NULL "
|
|
109
|
+
"WHERE user_playbook_id=?"
|
|
110
|
+
)
|
|
111
|
+
# agent_playbook purge not yet required; added when Task 3/4 needs it.
|
|
112
|
+
_PURGE_SQL: dict[str, str] = {
|
|
113
|
+
"profile": _PROFILE_PURGE_SQL,
|
|
114
|
+
"user_playbook": _USER_PLAYBOOK_PURGE_SQL,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SQLiteLineageMixin:
|
|
119
|
+
"""SQLite implementation of the append-only, content-free lineage event log."""
|
|
120
|
+
|
|
121
|
+
# Type hints for instance attributes provided by SQLiteStorageBase via MRO.
|
|
122
|
+
conn: sqlite3.Connection
|
|
123
|
+
_lock: threading.RLock
|
|
124
|
+
org_id: str
|
|
125
|
+
|
|
126
|
+
def append_lineage_event(self, event: LineageEvent) -> int:
|
|
127
|
+
"""Append an event; idempotent on (org_id, entity_type, entity_id, op, request_id).
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
event (LineageEvent): The fully-formed event to persist. ``event_id``
|
|
131
|
+
may be 0; the storage layer assigns a real id on insert. On a
|
|
132
|
+
duplicate ``(org_id, entity_type, entity_id, op, request_id)`` the
|
|
133
|
+
existing row is returned unchanged.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
int: The assigned or existing ``event_id``.
|
|
137
|
+
"""
|
|
138
|
+
created = event.created_at or int(time.time())
|
|
139
|
+
with self._lock:
|
|
140
|
+
cur = _append_event_stmt(
|
|
141
|
+
self.conn,
|
|
142
|
+
org_id=event.org_id,
|
|
143
|
+
entity_type=event.entity_type,
|
|
144
|
+
entity_id=event.entity_id,
|
|
145
|
+
op=event.op,
|
|
146
|
+
prov=event.prov_relation,
|
|
147
|
+
source_ids=event.source_ids,
|
|
148
|
+
actor=event.actor,
|
|
149
|
+
request_id=event.request_id,
|
|
150
|
+
reason=event.reason,
|
|
151
|
+
created_at=created,
|
|
152
|
+
from_status=event.from_status,
|
|
153
|
+
to_status=event.to_status,
|
|
154
|
+
status_namespace=event.status_namespace,
|
|
155
|
+
)
|
|
156
|
+
if (
|
|
157
|
+
cur.rowcount == 0
|
|
158
|
+
): # duplicate (org_id, entity_type, entity_id, op, request_id)
|
|
159
|
+
row = self.conn.execute(
|
|
160
|
+
"SELECT event_id FROM lineage_event WHERE org_id=? AND entity_type=? "
|
|
161
|
+
"AND entity_id=? AND op=? AND request_id=?",
|
|
162
|
+
(
|
|
163
|
+
event.org_id,
|
|
164
|
+
event.entity_type,
|
|
165
|
+
event.entity_id,
|
|
166
|
+
event.op,
|
|
167
|
+
event.request_id,
|
|
168
|
+
),
|
|
169
|
+
).fetchone()
|
|
170
|
+
eid = row[0] if row else None
|
|
171
|
+
self.conn.commit()
|
|
172
|
+
return int(eid) if eid is not None else 0
|
|
173
|
+
last = cur.lastrowid
|
|
174
|
+
self.conn.commit()
|
|
175
|
+
return int(last) if last is not None else 0
|
|
176
|
+
|
|
177
|
+
def get_lineage_events(
|
|
178
|
+
self,
|
|
179
|
+
*,
|
|
180
|
+
entity_type: str | None = None,
|
|
181
|
+
entity_id: str | None = None,
|
|
182
|
+
org_id: str | None = None,
|
|
183
|
+
request_id: str | None = None,
|
|
184
|
+
) -> list[LineageEvent]:
|
|
185
|
+
"""Retrieve lineage events, optionally filtered.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
entity_type (str | None): Filter to events for this entity type.
|
|
189
|
+
entity_id (str | None): Filter to events for this entity id.
|
|
190
|
+
org_id (str | None): Filter to events for this org.
|
|
191
|
+
request_id (str | None): Filter to events for this request id.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
list[LineageEvent]: Matching events ordered by ``event_id`` ascending.
|
|
195
|
+
"""
|
|
196
|
+
clauses: list[str] = []
|
|
197
|
+
params: list[Any] = []
|
|
198
|
+
for col, val in (
|
|
199
|
+
("entity_type", entity_type),
|
|
200
|
+
("entity_id", entity_id),
|
|
201
|
+
("org_id", org_id),
|
|
202
|
+
("request_id", request_id),
|
|
203
|
+
):
|
|
204
|
+
if val is not None:
|
|
205
|
+
clauses.append(f"{col}=?")
|
|
206
|
+
params.append(val)
|
|
207
|
+
where = (" WHERE " + " AND ".join(clauses)) if clauses else ""
|
|
208
|
+
with self._lock:
|
|
209
|
+
rows = self.conn.execute(
|
|
210
|
+
f"SELECT * FROM lineage_event{where} ORDER BY event_id", # noqa: S608
|
|
211
|
+
params,
|
|
212
|
+
).fetchall()
|
|
213
|
+
return [
|
|
214
|
+
LineageEvent(
|
|
215
|
+
event_id=r["event_id"],
|
|
216
|
+
org_id=r["org_id"],
|
|
217
|
+
entity_type=r["entity_type"],
|
|
218
|
+
entity_id=r["entity_id"],
|
|
219
|
+
op=r["op"],
|
|
220
|
+
prov_relation=r["prov_relation"],
|
|
221
|
+
source_ids=json.loads(r["source_ids"]),
|
|
222
|
+
actor=r["actor"],
|
|
223
|
+
request_id=r["request_id"],
|
|
224
|
+
reason=r["reason"],
|
|
225
|
+
created_at=r["created_at"],
|
|
226
|
+
from_status=r["from_status"],
|
|
227
|
+
to_status=r["to_status"],
|
|
228
|
+
status_namespace=r["status_namespace"],
|
|
229
|
+
)
|
|
230
|
+
for r in rows
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
def merge_records(
|
|
234
|
+
self,
|
|
235
|
+
*,
|
|
236
|
+
entity_type: EntityType,
|
|
237
|
+
survivor_id: str,
|
|
238
|
+
source_ids: list[str],
|
|
239
|
+
context: LineageContext,
|
|
240
|
+
) -> None:
|
|
241
|
+
"""Soft-delete each source into the survivor in one atomic transaction.
|
|
242
|
+
|
|
243
|
+
Sets ``status=MERGED`` and ``merged_into=survivor_id`` on each source
|
|
244
|
+
whose status is not already a tombstone. Appends a single ``merge``
|
|
245
|
+
lineage event keyed on ``survivor_id``. Idempotent — re-running on
|
|
246
|
+
already-tombstoned sources is a no-op.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
entity_type (str): One of ``"user_playbook"``, ``"agent_playbook"``,
|
|
250
|
+
or ``"profile"``.
|
|
251
|
+
survivor_id (str): The id of the record that survives the merge.
|
|
252
|
+
source_ids (list[str]): Ids of records to tombstone as merged.
|
|
253
|
+
context (LineageContext): Caller-supplied intent (actor, reason, etc.).
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
ValueError: If ``entity_type`` is not a recognized entity type.
|
|
257
|
+
ValueError: If ``context.request_id`` is empty or whitespace-only.
|
|
258
|
+
"""
|
|
259
|
+
if not (context.request_id and context.request_id.strip()):
|
|
260
|
+
raise ValueError(f"lineage merge: {_EMPTY_REQUEST_ID_MSG}")
|
|
261
|
+
table, pk = _resolve_table(entity_type)
|
|
262
|
+
now = _epoch_now()
|
|
263
|
+
eligible_ph = ",".join("?" * len(_GC_ELIGIBLE_STATUSES))
|
|
264
|
+
eligible_vals = list(_GC_ELIGIBLE_STATUSES)
|
|
265
|
+
with self._lock:
|
|
266
|
+
for sid in source_ids:
|
|
267
|
+
if sid == survivor_id:
|
|
268
|
+
# Never tombstone the survivor itself, even if it is
|
|
269
|
+
# accidentally listed among the source ids.
|
|
270
|
+
continue
|
|
271
|
+
# Skip sources that already carry any eligible/tombstone status
|
|
272
|
+
# (MERGED, SUPERSEDED, or ARCHIVED) — avoids re-tombstoning an
|
|
273
|
+
# already-archived source and resetting its retired_at clock.
|
|
274
|
+
self.conn.execute(
|
|
275
|
+
f"UPDATE {table} SET status=?, merged_into=?, retired_at=? " # noqa: S608
|
|
276
|
+
f"WHERE {pk}=? AND {pk}!=? "
|
|
277
|
+
f"AND (status IS NULL OR status NOT IN ({eligible_ph}))",
|
|
278
|
+
(
|
|
279
|
+
Status.MERGED.value,
|
|
280
|
+
survivor_id,
|
|
281
|
+
now,
|
|
282
|
+
sid,
|
|
283
|
+
survivor_id,
|
|
284
|
+
*eligible_vals,
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
_append_event_stmt(
|
|
288
|
+
self.conn,
|
|
289
|
+
org_id=self.org_id,
|
|
290
|
+
entity_type=entity_type,
|
|
291
|
+
entity_id=survivor_id,
|
|
292
|
+
op="merge",
|
|
293
|
+
prov="wasDerivedFrom",
|
|
294
|
+
source_ids=source_ids,
|
|
295
|
+
actor=context.actor,
|
|
296
|
+
request_id=context.request_id,
|
|
297
|
+
reason=context.reason,
|
|
298
|
+
)
|
|
299
|
+
self.conn.commit()
|
|
300
|
+
|
|
301
|
+
def supersede_record(
|
|
302
|
+
self,
|
|
303
|
+
*,
|
|
304
|
+
entity_type: EntityType,
|
|
305
|
+
incumbent_id: str,
|
|
306
|
+
successor_id: str,
|
|
307
|
+
context: LineageContext,
|
|
308
|
+
) -> bool:
|
|
309
|
+
"""Atomically replace the incumbent with the successor if incumbent is CURRENT.
|
|
310
|
+
|
|
311
|
+
Sets ``status=SUPERSEDED`` and ``superseded_by=successor_id`` on the
|
|
312
|
+
incumbent **only** when its ``status IS NULL`` (CURRENT). Appends a
|
|
313
|
+
``revise`` lineage event when the guard succeeds. Returns ``False``
|
|
314
|
+
without mutating anything when the incumbent is not CURRENT.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
entity_type (str): One of ``"user_playbook"``, ``"agent_playbook"``,
|
|
318
|
+
or ``"profile"``.
|
|
319
|
+
incumbent_id (str): The id of the record to supersede.
|
|
320
|
+
successor_id (str): The id of the record that replaces the incumbent.
|
|
321
|
+
context (LineageContext): Caller-supplied intent (actor, reason, etc.).
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
bool: ``True`` if the incumbent was CURRENT and was superseded;
|
|
325
|
+
``False`` if the incumbent was not CURRENT and no mutation occurred.
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
ValueError: If ``entity_type`` is not a recognized entity type.
|
|
329
|
+
ValueError: If ``context.request_id`` is empty or whitespace-only.
|
|
330
|
+
"""
|
|
331
|
+
if not (context.request_id and context.request_id.strip()):
|
|
332
|
+
raise ValueError(f"lineage supersede: {_EMPTY_REQUEST_ID_MSG}")
|
|
333
|
+
table, pk = _resolve_table(entity_type)
|
|
334
|
+
with self._lock:
|
|
335
|
+
cur = self.conn.execute(
|
|
336
|
+
f"UPDATE {table} SET status=?, superseded_by=?, retired_at=? " # noqa: S608
|
|
337
|
+
f"WHERE {pk}=? AND status IS NULL",
|
|
338
|
+
(Status.SUPERSEDED.value, successor_id, _epoch_now(), incumbent_id),
|
|
339
|
+
)
|
|
340
|
+
if cur.rowcount == 0:
|
|
341
|
+
self.conn.commit()
|
|
342
|
+
return False
|
|
343
|
+
_append_event_stmt(
|
|
344
|
+
self.conn,
|
|
345
|
+
org_id=self.org_id,
|
|
346
|
+
entity_type=entity_type,
|
|
347
|
+
entity_id=successor_id,
|
|
348
|
+
op="revise",
|
|
349
|
+
prov="wasRevisionOf",
|
|
350
|
+
source_ids=[incumbent_id],
|
|
351
|
+
actor=context.actor,
|
|
352
|
+
request_id=context.request_id,
|
|
353
|
+
reason=context.reason,
|
|
354
|
+
)
|
|
355
|
+
self.conn.commit()
|
|
356
|
+
return True
|
|
357
|
+
|
|
358
|
+
def purge_content(self, *, entity_type: EntityType, entity_id: str) -> bool:
|
|
359
|
+
"""Blank a record's PII body, keep its lineage skeleton, emit op=purge.
|
|
360
|
+
|
|
361
|
+
Blanks every non-skeleton column for the row identified by ``entity_id``,
|
|
362
|
+
emits one ``op=purge`` lineage event in the same commit, then runs FTS/vec
|
|
363
|
+
index cleanup after the commit. The ``request_id`` is deterministic
|
|
364
|
+
(``"purge_" + entity_id``) so re-runs on an already-blank row do not
|
|
365
|
+
produce a duplicate event (``INSERT OR IGNORE`` on the unique key).
|
|
366
|
+
|
|
367
|
+
Re-running on a row whose content is already blank is safe: the UPDATE
|
|
368
|
+
still matches the row and sets the same values (harmless idempotent write),
|
|
369
|
+
while the INSERT OR IGNORE deduplicates the event on
|
|
370
|
+
``(org_id, entity_type, entity_id, op, request_id)``.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
entity_type (EntityType): One of ``"user_playbook"`` or ``"profile"``.
|
|
374
|
+
``"agent_playbook"`` raises ``ValueError`` — agent playbooks have no
|
|
375
|
+
``user_id`` and are out of scope for content purge.
|
|
376
|
+
entity_id (str): The entity's primary key as a string.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
bool: ``True`` if the row exists; ``False`` if the id had no matching row.
|
|
380
|
+
|
|
381
|
+
Raises:
|
|
382
|
+
ValueError: If ``entity_type`` is not a recognized entity type or
|
|
383
|
+
if ``entity_type`` is ``"agent_playbook"`` (not supported).
|
|
384
|
+
"""
|
|
385
|
+
sql = _PURGE_SQL.get(entity_type)
|
|
386
|
+
if sql is None:
|
|
387
|
+
raise ValueError(f"purge_content: unsupported entity_type {entity_type!r}")
|
|
388
|
+
table, pk = _resolve_table(entity_type)
|
|
389
|
+
with self._lock:
|
|
390
|
+
row = self.conn.execute(
|
|
391
|
+
f"SELECT rowid AS _rowid FROM {table} WHERE {pk}=?", # noqa: S608
|
|
392
|
+
(entity_id,),
|
|
393
|
+
).fetchone()
|
|
394
|
+
if row is None:
|
|
395
|
+
return False
|
|
396
|
+
rowid = row["_rowid"]
|
|
397
|
+
cur = self.conn.execute(sql, (entity_id,))
|
|
398
|
+
if cur.rowcount > 0:
|
|
399
|
+
# Row matched (rowcount==1 whenever it exists); attempt the event —
|
|
400
|
+
# INSERT OR IGNORE dedups on the deterministic request_id.
|
|
401
|
+
_append_event_stmt(
|
|
402
|
+
self.conn,
|
|
403
|
+
org_id=self.org_id,
|
|
404
|
+
entity_type=entity_type,
|
|
405
|
+
entity_id=entity_id,
|
|
406
|
+
op="purge",
|
|
407
|
+
prov="wasPurged",
|
|
408
|
+
source_ids=[],
|
|
409
|
+
actor="erasure", # no user identifier
|
|
410
|
+
request_id=f"purge_{entity_id}", # deterministic → idempotent
|
|
411
|
+
reason="content_purge",
|
|
412
|
+
)
|
|
413
|
+
self.conn.commit()
|
|
414
|
+
# Post-commit index cleanup (self-committing helpers; idempotent/replayable).
|
|
415
|
+
self._purge_search_indexes(entity_type, entity_id, rowid)
|
|
416
|
+
return True
|
|
417
|
+
|
|
418
|
+
def _purge_search_indexes(
|
|
419
|
+
self, entity_type: EntityType, entity_id: str, rowid: int
|
|
420
|
+
) -> None:
|
|
421
|
+
"""Remove FTS and vec index entries for a purged entity.
|
|
422
|
+
|
|
423
|
+
Called after the blanking commit so index cleanup never interleaves with
|
|
424
|
+
the atomic mutation+event transaction.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
entity_type (EntityType): One of ``"user_playbook"`` or ``"profile"``.
|
|
428
|
+
entity_id (str): The entity's primary key (used for profiles FTS).
|
|
429
|
+
rowid (int): The sqlite rowid (used for playbooks FTS and all vec tables).
|
|
430
|
+
"""
|
|
431
|
+
if entity_type == "profile":
|
|
432
|
+
self._fts_delete_profile(entity_id) # type: ignore[attr-defined]
|
|
433
|
+
if self._has_sqlite_vec: # type: ignore[attr-defined]
|
|
434
|
+
self._vec_delete("profiles_vec", rowid) # type: ignore[attr-defined]
|
|
435
|
+
elif entity_type == "user_playbook":
|
|
436
|
+
self._fts_delete("user_playbooks_fts", rowid) # type: ignore[attr-defined]
|
|
437
|
+
if self._has_sqlite_vec: # type: ignore[attr-defined]
|
|
438
|
+
self._vec_delete("user_playbooks_vec", rowid) # type: ignore[attr-defined]
|
|
439
|
+
|
|
440
|
+
def has_inbound_lineage_refs(
|
|
441
|
+
self, *, entity_type: EntityType, entity_id: str
|
|
442
|
+
) -> bool:
|
|
443
|
+
"""Return True if any row points at ``entity_id`` via merged_into/superseded_by.
|
|
444
|
+
|
|
445
|
+
Org-scoped but deliberately NOT user_id-scoped: a cross-user chain
|
|
446
|
+
(one user's tombstone pointing at another user's survivor) must be
|
|
447
|
+
detected so the survivor is purged, not hard-deleted, on erasure.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
entity_type (EntityType): One of ``"user_playbook"``, ``"agent_playbook"``,
|
|
451
|
+
or ``"profile"``.
|
|
452
|
+
entity_id (str): The entity's primary key to check for inbound refs.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
bool: True if any row has ``merged_into == entity_id``
|
|
456
|
+
OR ``superseded_by == entity_id``; False otherwise.
|
|
457
|
+
"""
|
|
458
|
+
table, _pk = _resolve_table(entity_type)
|
|
459
|
+
with self._lock:
|
|
460
|
+
row = self.conn.execute(
|
|
461
|
+
f"SELECT 1 FROM {table} " # noqa: S608
|
|
462
|
+
f"WHERE merged_into = ? OR superseded_by = ? LIMIT 1",
|
|
463
|
+
(entity_id, entity_id),
|
|
464
|
+
).fetchone()
|
|
465
|
+
return row is not None
|
|
466
|
+
|
|
467
|
+
def _is_on_legal_hold(
|
|
468
|
+
self,
|
|
469
|
+
org_id: str, # noqa: ARG002
|
|
470
|
+
entity_type: str, # noqa: ARG002
|
|
471
|
+
entity_id: str, # noqa: ARG002
|
|
472
|
+
) -> bool:
|
|
473
|
+
"""Return True if this entity is under a legal hold and must not be GC'd.
|
|
474
|
+
|
|
475
|
+
Deferred seam — always returns False until a hold store exists.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
org_id (str): The organisation that owns the entity.
|
|
479
|
+
entity_type (str): One of ``"user_playbook"``, ``"agent_playbook"``,
|
|
480
|
+
or ``"profile"``.
|
|
481
|
+
entity_id (str): The entity's primary key as a string.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
bool: False (no hold store implemented yet).
|
|
485
|
+
"""
|
|
486
|
+
return False
|
|
487
|
+
|
|
488
|
+
def list_org_ids(self) -> list[str]:
|
|
489
|
+
"""Return the single org_id for this SQLite storage instance.
|
|
490
|
+
|
|
491
|
+
SQLite storage is single-tenant: each instance is scoped to exactly one
|
|
492
|
+
org. Returns ``[self.org_id]``.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
list[str]: A one-element list containing this instance's org_id.
|
|
496
|
+
"""
|
|
497
|
+
return [self.org_id]
|
|
498
|
+
|
|
499
|
+
def gc_expired_tombstones(
|
|
500
|
+
self, *, entity_type: str, older_than_epoch: int, limit: int = 1000
|
|
501
|
+
) -> int:
|
|
502
|
+
"""Hard-delete tombstone rows whose retirement instant is older than the cutoff.
|
|
503
|
+
|
|
504
|
+
Ages on the uniform INTEGER ``retired_at`` column set at every tombstone
|
|
505
|
+
write-path (T1). Rows with ``retired_at = NULL`` (pre-T1 tombstones) are
|
|
506
|
+
never selected — they have no retirement clock and must be retained.
|
|
507
|
+
|
|
508
|
+
Emits one ``hard_delete`` lineage event per deleted row, atomically, before
|
|
509
|
+
the DELETE commits. Rows on legal hold are skipped without emitting an event.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
entity_type (str): One of ``"user_playbook"``, ``"agent_playbook"``,
|
|
513
|
+
or ``"profile"``.
|
|
514
|
+
older_than_epoch (int): Unix timestamp cutoff (exclusive). Rows whose
|
|
515
|
+
``retired_at`` is strictly less than this value are eligible.
|
|
516
|
+
limit (int): Maximum rows to delete per call. Defaults to 1000.
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
int: The number of rows physically deleted.
|
|
520
|
+
|
|
521
|
+
Raises:
|
|
522
|
+
ValueError: If ``entity_type`` is not a recognised entity type.
|
|
523
|
+
"""
|
|
524
|
+
if limit <= 0:
|
|
525
|
+
return 0
|
|
526
|
+
table, pk = _resolve_table(entity_type)
|
|
527
|
+
|
|
528
|
+
eligible_ph = ",".join("?" * len(_GC_ELIGIBLE_STATUSES))
|
|
529
|
+
eligible_vals = list(_GC_ELIGIBLE_STATUSES)
|
|
530
|
+
|
|
531
|
+
# ORDER BY retired_at ASC for deterministic forward progress.
|
|
532
|
+
# No SQL LIMIT here — the limit is applied after the legal-hold filter
|
|
533
|
+
# below so held rows at the front of the batch don't starve eligible rows.
|
|
534
|
+
select_sql = (
|
|
535
|
+
f"SELECT {pk} FROM {table} " # noqa: S608
|
|
536
|
+
f"WHERE status IN ({eligible_ph}) "
|
|
537
|
+
f"AND retired_at IS NOT NULL AND retired_at < ? "
|
|
538
|
+
f"ORDER BY retired_at ASC"
|
|
539
|
+
)
|
|
540
|
+
select_params: list[Any] = [*eligible_vals, older_than_epoch]
|
|
541
|
+
|
|
542
|
+
with self._lock:
|
|
543
|
+
rows = self.conn.execute(select_sql, select_params).fetchall()
|
|
544
|
+
if not rows:
|
|
545
|
+
return 0
|
|
546
|
+
|
|
547
|
+
candidate_ids: list[str] = [str(r[0]) for r in rows]
|
|
548
|
+
ids_to_delete: list[str] = []
|
|
549
|
+
for eid in candidate_ids:
|
|
550
|
+
if self._is_on_legal_hold(self.org_id, entity_type, eid):
|
|
551
|
+
# NOTE: any real hold-check implementation must run inside
|
|
552
|
+
# the same transaction as the DELETE to remain atomic.
|
|
553
|
+
capture_anomaly(
|
|
554
|
+
"lineage.gc.legal_hold_skip",
|
|
555
|
+
level="info",
|
|
556
|
+
org_id=self.org_id,
|
|
557
|
+
entity_type=entity_type,
|
|
558
|
+
entity_id=eid,
|
|
559
|
+
)
|
|
560
|
+
continue
|
|
561
|
+
ids_to_delete.append(eid)
|
|
562
|
+
if len(ids_to_delete) >= limit:
|
|
563
|
+
break
|
|
564
|
+
|
|
565
|
+
if not ids_to_delete:
|
|
566
|
+
return 0
|
|
567
|
+
|
|
568
|
+
batch_request_id = uuid.uuid4().hex
|
|
569
|
+
ph = ",".join("?" * len(ids_to_delete))
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
# Emit hard_delete events BEFORE the DELETE, in the same transaction.
|
|
573
|
+
for eid in ids_to_delete:
|
|
574
|
+
_append_event_stmt(
|
|
575
|
+
self.conn,
|
|
576
|
+
org_id=self.org_id,
|
|
577
|
+
entity_type=entity_type,
|
|
578
|
+
entity_id=eid,
|
|
579
|
+
op="hard_delete",
|
|
580
|
+
prov="wasInvalidatedBy",
|
|
581
|
+
source_ids=[],
|
|
582
|
+
actor="system",
|
|
583
|
+
request_id=batch_request_id,
|
|
584
|
+
reason="ttl-gc",
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
# Inline FTS/vec cleanup — raw DELETE to preserve atomicity.
|
|
588
|
+
# Do NOT call self._fts_delete/_vec_delete: they self-commit.
|
|
589
|
+
if entity_type in ("user_playbook", "agent_playbook"):
|
|
590
|
+
kind = "user" if entity_type == "user_playbook" else "agent"
|
|
591
|
+
int_ids = [int(eid) for eid in ids_to_delete]
|
|
592
|
+
int_ph = ",".join("?" * len(int_ids))
|
|
593
|
+
self.conn.execute(
|
|
594
|
+
f"DELETE FROM {kind}_playbooks_fts WHERE rowid IN ({int_ph})",
|
|
595
|
+
int_ids,
|
|
596
|
+
)
|
|
597
|
+
if self._has_sqlite_vec: # type: ignore[attr-defined]
|
|
598
|
+
self.conn.execute(
|
|
599
|
+
f"DELETE FROM {kind}_playbooks_vec WHERE rowid IN ({int_ph})",
|
|
600
|
+
int_ids,
|
|
601
|
+
)
|
|
602
|
+
else:
|
|
603
|
+
# profiles: FTS keyed on TEXT profile_id; vec keyed on implicit rowid.
|
|
604
|
+
self.conn.execute(
|
|
605
|
+
f"DELETE FROM profiles_fts WHERE profile_id IN ({ph})",
|
|
606
|
+
ids_to_delete,
|
|
607
|
+
)
|
|
608
|
+
if self._has_sqlite_vec: # type: ignore[attr-defined]
|
|
609
|
+
rowid_rows = self.conn.execute(
|
|
610
|
+
f"SELECT rowid FROM profiles WHERE profile_id IN ({ph})", # noqa: S608
|
|
611
|
+
ids_to_delete,
|
|
612
|
+
).fetchall()
|
|
613
|
+
if rowid_rows:
|
|
614
|
+
rowids = [r[0] for r in rowid_rows]
|
|
615
|
+
rowid_ph = ",".join("?" * len(rowids))
|
|
616
|
+
self.conn.execute(
|
|
617
|
+
f"DELETE FROM profiles_vec WHERE rowid IN ({rowid_ph})",
|
|
618
|
+
rowids,
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
cur = self.conn.execute(
|
|
622
|
+
f"DELETE FROM {table} WHERE {pk} IN ({ph})", # noqa: S608
|
|
623
|
+
ids_to_delete,
|
|
624
|
+
)
|
|
625
|
+
self.conn.commit()
|
|
626
|
+
except Exception:
|
|
627
|
+
self.conn.rollback()
|
|
628
|
+
raise
|
|
629
|
+
|
|
630
|
+
return cur.rowcount
|