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
|
@@ -14,7 +14,7 @@ import math
|
|
|
14
14
|
import re
|
|
15
15
|
import sqlite3
|
|
16
16
|
import threading
|
|
17
|
-
from collections.abc import Callable
|
|
17
|
+
from collections.abc import Callable, Sequence
|
|
18
18
|
from datetime import UTC, datetime
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Any, ClassVar, Literal
|
|
@@ -22,14 +22,10 @@ from typing import Any, ClassVar, Literal
|
|
|
22
22
|
from reflexio.models.api_schema.common import BlockingIssue
|
|
23
23
|
from reflexio.models.api_schema.service_schemas import (
|
|
24
24
|
AgentPlaybook,
|
|
25
|
-
AgentPlaybookSnapshot,
|
|
26
|
-
AgentPlaybookUpdateEntry,
|
|
27
25
|
AgentSuccessEvaluationResult,
|
|
28
26
|
Citation,
|
|
29
27
|
Interaction,
|
|
30
|
-
PlaybookAggregationChangeLog,
|
|
31
28
|
PlaybookStatus,
|
|
32
|
-
ProfileChangeLog,
|
|
33
29
|
ProfileTimeToLive,
|
|
34
30
|
RegularVsShadow,
|
|
35
31
|
Request,
|
|
@@ -53,7 +49,10 @@ from reflexio.server.llm.model_defaults import (
|
|
|
53
49
|
from reflexio.server.llm.providers.embedding_service_provider import (
|
|
54
50
|
EmbeddingUnavailableError,
|
|
55
51
|
)
|
|
56
|
-
from reflexio.server.services.storage.error import
|
|
52
|
+
from reflexio.server.services.storage.error import (
|
|
53
|
+
StorageError,
|
|
54
|
+
require_non_empty_session_id,
|
|
55
|
+
)
|
|
57
56
|
from reflexio.server.services.storage.retention import RetentionTarget
|
|
58
57
|
from reflexio.server.services.storage.retention_mixin import (
|
|
59
58
|
RETENTION_DELETE_CHUNK,
|
|
@@ -63,6 +62,7 @@ from reflexio.server.services.storage.retention_mixin import (
|
|
|
63
62
|
from reflexio.server.services.storage.storage_base import BaseStorage
|
|
64
63
|
from reflexio.server.site_var.site_var_manager import SiteVarManager
|
|
65
64
|
|
|
65
|
+
from ._governance import init_governance_tables
|
|
66
66
|
from ._stall_state import init_stall_state_table
|
|
67
67
|
|
|
68
68
|
logger = logging.getLogger(__name__)
|
|
@@ -182,10 +182,10 @@ def _effective_search_mode(
|
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
def _vector_rank_rows(
|
|
185
|
-
rows:
|
|
185
|
+
rows: Sequence[Any],
|
|
186
186
|
query_embedding: list[float],
|
|
187
187
|
match_count: int,
|
|
188
|
-
) -> list[
|
|
188
|
+
) -> list[Any]:
|
|
189
189
|
"""Rank rows by cosine similarity to the query embedding.
|
|
190
190
|
|
|
191
191
|
Args:
|
|
@@ -196,7 +196,7 @@ def _vector_rank_rows(
|
|
|
196
196
|
Returns:
|
|
197
197
|
Top ``match_count`` rows sorted by cosine similarity descending.
|
|
198
198
|
"""
|
|
199
|
-
scored: list[tuple[
|
|
199
|
+
scored: list[tuple[Any, float]] = []
|
|
200
200
|
for row in rows:
|
|
201
201
|
raw_emb = row["embedding"] if "embedding" in row.keys() else None # noqa: SIM118
|
|
202
202
|
emb = _json_loads(raw_emb) if raw_emb else None
|
|
@@ -225,14 +225,14 @@ def _vector_rank_rows(
|
|
|
225
225
|
|
|
226
226
|
|
|
227
227
|
def _true_rrf_merge(
|
|
228
|
-
fts_rows:
|
|
229
|
-
vec_rows:
|
|
228
|
+
fts_rows: Sequence[Any],
|
|
229
|
+
vec_rows: Sequence[Any],
|
|
230
230
|
id_column: str,
|
|
231
231
|
match_count: int,
|
|
232
232
|
rrf_k: int = 60,
|
|
233
233
|
vector_weight: float = 1.0,
|
|
234
234
|
fts_weight: float = 1.0,
|
|
235
|
-
) -> list[
|
|
235
|
+
) -> list[Any]:
|
|
236
236
|
"""Merge independent FTS and vector result sets via Reciprocal Rank Fusion.
|
|
237
237
|
|
|
238
238
|
Unlike ``_rrf_rerank`` (which re-ranks FTS results only), this function
|
|
@@ -255,7 +255,7 @@ def _true_rrf_merge(
|
|
|
255
255
|
return []
|
|
256
256
|
|
|
257
257
|
# Collect unique rows by ID (first-seen wins for the Row object)
|
|
258
|
-
row_by_id: dict[str | int,
|
|
258
|
+
row_by_id: dict[str | int, Any] = {}
|
|
259
259
|
for row in (*fts_rows, *vec_rows):
|
|
260
260
|
rid = row[id_column]
|
|
261
261
|
if rid not in row_by_id:
|
|
@@ -271,7 +271,7 @@ def _true_rrf_merge(
|
|
|
271
271
|
fts_penalty = len(fts_rows) + 1
|
|
272
272
|
vec_penalty = len(vec_rows) + 1
|
|
273
273
|
|
|
274
|
-
scored: list[tuple[
|
|
274
|
+
scored: list[tuple[Any, float]] = []
|
|
275
275
|
for rid, row in row_by_id.items():
|
|
276
276
|
f_rank = fts_rank.get(rid, fts_penalty)
|
|
277
277
|
v_rank = vec_rank.get(rid, vec_penalty)
|
|
@@ -282,6 +282,13 @@ def _true_rrf_merge(
|
|
|
282
282
|
return [row for row, _ in scored[:match_count]]
|
|
283
283
|
|
|
284
284
|
|
|
285
|
+
# Tombstone statuses: rows with these values are excluded from default reads.
|
|
286
|
+
# Tasks 5/9/10 create tombstones; this constant ensures they stay hidden unless
|
|
287
|
+
# explicitly requested via include_tombstones=True on by-id getters, or an
|
|
288
|
+
# explicit status_filter on list/count methods.
|
|
289
|
+
_TOMBSTONE_STATUS_VALUES = (Status.MERGED.value, Status.SUPERSEDED.value)
|
|
290
|
+
|
|
291
|
+
|
|
285
292
|
def _status_value(status: Status | None) -> str | None:
|
|
286
293
|
"""Convert a Status enum (or None) to its DB string value."""
|
|
287
294
|
if status is None:
|
|
@@ -340,14 +347,32 @@ def _iso_to_epoch(iso_str: str | None) -> int:
|
|
|
340
347
|
return _epoch_now()
|
|
341
348
|
try:
|
|
342
349
|
cleaned = iso_str.replace("Z", "+00:00")
|
|
343
|
-
|
|
350
|
+
parsed = datetime.fromisoformat(cleaned)
|
|
351
|
+
if parsed.tzinfo is None:
|
|
352
|
+
parsed = parsed.replace(tzinfo=UTC)
|
|
353
|
+
return int(parsed.timestamp())
|
|
344
354
|
except (ValueError, TypeError):
|
|
345
355
|
return _epoch_now()
|
|
346
356
|
|
|
347
357
|
|
|
358
|
+
# Bounds that ``datetime.fromtimestamp(tz=UTC)`` can represent (year 1..9999).
|
|
359
|
+
# Callers pass sentinel "open" bounds — e.g. ``to_ts=10**12`` for "no upper
|
|
360
|
+
# limit" or ``0`` for "from the beginning" — which would otherwise overflow
|
|
361
|
+
# ``datetime.fromtimestamp`` with a ``ValueError``. Clamping to these bounds
|
|
362
|
+
# yields the same query semantics (the ISO string still sorts before/after every
|
|
363
|
+
# stored row) with a valid value.
|
|
364
|
+
_MAX_SAFE_EPOCH_TS = 253_402_300_799 # 9999-12-31T23:59:59Z
|
|
365
|
+
_MIN_SAFE_EPOCH_TS = 0 # 1970-01-01T00:00:00Z
|
|
366
|
+
|
|
367
|
+
|
|
348
368
|
def _epoch_to_iso(ts: int) -> str:
|
|
349
|
-
"""Convert a Unix timestamp to ISO 8601 string.
|
|
350
|
-
|
|
369
|
+
"""Convert a Unix timestamp (seconds) to an ISO 8601 string.
|
|
370
|
+
|
|
371
|
+
Out-of-range sentinel bounds are clamped to the representable range so that
|
|
372
|
+
callers passing "open" window bounds never trigger a ``ValueError``.
|
|
373
|
+
"""
|
|
374
|
+
clamped = max(_MIN_SAFE_EPOCH_TS, min(ts, _MAX_SAFE_EPOCH_TS))
|
|
375
|
+
return datetime.fromtimestamp(clamped, tz=UTC).isoformat()
|
|
351
376
|
|
|
352
377
|
|
|
353
378
|
# ---------------------------------------------------------------------------
|
|
@@ -373,6 +398,10 @@ def _row_to_profile(row: sqlite3.Row) -> UserProfile:
|
|
|
373
398
|
source_span=d.get("source_span"),
|
|
374
399
|
notes=d.get("notes"),
|
|
375
400
|
reader_angle=d.get("reader_angle"),
|
|
401
|
+
tags=_json_loads(d.get("tags")),
|
|
402
|
+
source_interaction_ids=_json_loads(d.get("source_interaction_ids")) or [],
|
|
403
|
+
merged_into=d.get("merged_into"),
|
|
404
|
+
superseded_by=d.get("superseded_by"),
|
|
376
405
|
)
|
|
377
406
|
|
|
378
407
|
|
|
@@ -400,6 +429,7 @@ def _row_to_interaction(row: sqlite3.Row) -> Interaction:
|
|
|
400
429
|
user_action=UserActionType(d["user_action"]),
|
|
401
430
|
user_action_description=d["user_action_description"],
|
|
402
431
|
interacted_image_url=d["interacted_image_url"],
|
|
432
|
+
image_encoding=d.get("image_encoding") or "",
|
|
403
433
|
shadow_content=d.get("shadow_content") or "",
|
|
404
434
|
expert_content=d.get("expert_content") or "",
|
|
405
435
|
tools_used=tools_used,
|
|
@@ -409,24 +439,14 @@ def _row_to_interaction(row: sqlite3.Row) -> Interaction:
|
|
|
409
439
|
|
|
410
440
|
def _row_to_request(row: sqlite3.Row) -> Request:
|
|
411
441
|
d = dict(row)
|
|
412
|
-
metadata_raw = d.get("metadata") or "{}"
|
|
413
|
-
try:
|
|
414
|
-
parsed = _json_loads(metadata_raw)
|
|
415
|
-
metadata = parsed if isinstance(parsed, dict) else {}
|
|
416
|
-
except json.JSONDecodeError:
|
|
417
|
-
logger.warning(
|
|
418
|
-
"Malformed metadata JSON for request %s; defaulting to empty dict",
|
|
419
|
-
d.get("request_id"),
|
|
420
|
-
)
|
|
421
|
-
metadata = {}
|
|
422
442
|
return Request(
|
|
423
443
|
request_id=d["request_id"],
|
|
424
444
|
user_id=d["user_id"],
|
|
425
445
|
created_at=_iso_to_epoch(d["created_at"]),
|
|
426
446
|
source=d.get("source") or "",
|
|
427
447
|
agent_version=d.get("agent_version") or "",
|
|
428
|
-
session_id=d.get("session_id"),
|
|
429
|
-
|
|
448
|
+
session_id=require_non_empty_session_id(d.get("session_id")),
|
|
449
|
+
evaluation_only=bool(d.get("evaluation_only", 0)),
|
|
430
450
|
)
|
|
431
451
|
|
|
432
452
|
|
|
@@ -455,11 +475,14 @@ def _row_to_user_playbook(
|
|
|
455
475
|
status=Status(d["status"]) if d.get("status") else None,
|
|
456
476
|
source=d.get("source"),
|
|
457
477
|
source_interaction_ids=_json_loads(d.get("source_interaction_ids")) or [],
|
|
478
|
+
tags=_json_loads(d.get("tags")),
|
|
458
479
|
embedding=embedding,
|
|
459
480
|
expanded_terms=d.get("expanded_terms"),
|
|
460
481
|
source_span=d.get("source_span"),
|
|
461
482
|
notes=d.get("notes"),
|
|
462
483
|
reader_angle=d.get("reader_angle"),
|
|
484
|
+
merged_into=d.get("merged_into"),
|
|
485
|
+
superseded_by=d.get("superseded_by"),
|
|
463
486
|
)
|
|
464
487
|
|
|
465
488
|
|
|
@@ -480,9 +503,12 @@ def _row_to_agent_playbook(row: sqlite3.Row) -> AgentPlaybook:
|
|
|
480
503
|
if d.get("playbook_status")
|
|
481
504
|
else PlaybookStatus.PENDING,
|
|
482
505
|
playbook_metadata=d.get("playbook_metadata") or "",
|
|
506
|
+
tags=_json_loads(d.get("tags")),
|
|
483
507
|
embedding=[],
|
|
484
508
|
status=Status(d["status"]) if d.get("status") else None,
|
|
485
509
|
expanded_terms=d.get("expanded_terms"),
|
|
510
|
+
merged_into=d.get("merged_into"),
|
|
511
|
+
superseded_by=d.get("superseded_by"),
|
|
486
512
|
)
|
|
487
513
|
|
|
488
514
|
|
|
@@ -490,6 +516,7 @@ def _row_to_eval_result(row: sqlite3.Row) -> AgentSuccessEvaluationResult:
|
|
|
490
516
|
d = dict(row)
|
|
491
517
|
return AgentSuccessEvaluationResult(
|
|
492
518
|
result_id=d["result_id"],
|
|
519
|
+
user_id=d.get("user_id") or "",
|
|
493
520
|
session_id=d["session_id"],
|
|
494
521
|
agent_version=d["agent_version"],
|
|
495
522
|
evaluation_name=d.get("evaluation_name"),
|
|
@@ -509,53 +536,6 @@ def _row_to_eval_result(row: sqlite3.Row) -> AgentSuccessEvaluationResult:
|
|
|
509
536
|
)
|
|
510
537
|
|
|
511
538
|
|
|
512
|
-
def _row_to_profile_change_log(row: sqlite3.Row) -> ProfileChangeLog:
|
|
513
|
-
d = dict(row)
|
|
514
|
-
return ProfileChangeLog(
|
|
515
|
-
id=d["id"],
|
|
516
|
-
user_id=d["user_id"],
|
|
517
|
-
request_id=d["request_id"],
|
|
518
|
-
created_at=d["created_at"],
|
|
519
|
-
added_profiles=[
|
|
520
|
-
UserProfile(**p) for p in (_json_loads(d["added_profiles"]) or [])
|
|
521
|
-
],
|
|
522
|
-
removed_profiles=[
|
|
523
|
-
UserProfile(**p) for p in (_json_loads(d["removed_profiles"]) or [])
|
|
524
|
-
],
|
|
525
|
-
mentioned_profiles=[
|
|
526
|
-
UserProfile(**p) for p in (_json_loads(d["mentioned_profiles"]) or [])
|
|
527
|
-
],
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
def _row_to_playbook_aggregation_change_log(
|
|
532
|
-
row: sqlite3.Row,
|
|
533
|
-
) -> PlaybookAggregationChangeLog:
|
|
534
|
-
d = dict(row)
|
|
535
|
-
return PlaybookAggregationChangeLog(
|
|
536
|
-
id=d["id"],
|
|
537
|
-
created_at=d["created_at"],
|
|
538
|
-
playbook_name=d["playbook_name"],
|
|
539
|
-
agent_version=d["agent_version"],
|
|
540
|
-
run_mode=d["run_mode"],
|
|
541
|
-
added_agent_playbooks=[
|
|
542
|
-
AgentPlaybookSnapshot(**fb)
|
|
543
|
-
for fb in (_json_loads(d.get("added_playbooks")) or [])
|
|
544
|
-
],
|
|
545
|
-
removed_agent_playbooks=[
|
|
546
|
-
AgentPlaybookSnapshot(**fb)
|
|
547
|
-
for fb in (_json_loads(d.get("removed_playbooks")) or [])
|
|
548
|
-
],
|
|
549
|
-
updated_agent_playbooks=[
|
|
550
|
-
AgentPlaybookUpdateEntry(
|
|
551
|
-
before=AgentPlaybookSnapshot(**entry["before"]),
|
|
552
|
-
after=AgentPlaybookSnapshot(**entry["after"]),
|
|
553
|
-
)
|
|
554
|
-
for entry in (_json_loads(d.get("updated_playbooks")) or [])
|
|
555
|
-
],
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
|
|
559
539
|
# ---------------------------------------------------------------------------
|
|
560
540
|
# SQLiteStorageBase
|
|
561
541
|
# ---------------------------------------------------------------------------
|
|
@@ -652,9 +632,18 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
652
632
|
def migrate(self) -> bool:
|
|
653
633
|
self._migrate_feedback_schema()
|
|
654
634
|
self._migrate_interactions_schema()
|
|
635
|
+
# Backfill columns that _DDL indexes depend on BEFORE running _DDL.
|
|
636
|
+
# _DDL builds idx_eval_identity_created_at_desc on
|
|
637
|
+
# agent_success_evaluation_result(user_id, ...). On a pre-existing DB that
|
|
638
|
+
# table predates user_id, so executescript(_DDL) raises "no such column:
|
|
639
|
+
# user_id" and migrate() never reaches the backfill below — leaving the DB
|
|
640
|
+
# permanently stuck. The helper is guarded (no-ops when the table is
|
|
641
|
+
# absent), so running it before _DDL is safe on fresh databases too.
|
|
642
|
+
self._migrate_eval_result_user_id()
|
|
655
643
|
with self._lock:
|
|
656
644
|
cur = self.conn.cursor()
|
|
657
645
|
cur.executescript(_DDL)
|
|
646
|
+
init_governance_tables(self.conn)
|
|
658
647
|
self.conn.commit()
|
|
659
648
|
if self._has_sqlite_vec:
|
|
660
649
|
self._create_vec_tables()
|
|
@@ -663,11 +652,22 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
663
652
|
self._migrate_agent_runs_schema()
|
|
664
653
|
self._migrate_pending_tool_calls_schema()
|
|
665
654
|
self._migrate_expanded_terms()
|
|
655
|
+
self._migrate_tags()
|
|
656
|
+
self._migrate_profile_source_interaction_ids()
|
|
657
|
+
self._migrate_interaction_window_indexes()
|
|
666
658
|
self._migrate_agentic_signals()
|
|
667
659
|
self._migrate_agent_playbook_source_windows()
|
|
668
|
-
self.
|
|
660
|
+
self._migrate_request_evaluation_only()
|
|
661
|
+
self._migrate_request_session_id_required()
|
|
662
|
+
# _migrate_eval_result_user_id() runs before _DDL (see above).
|
|
669
663
|
self._migrate_shadow_comparison_verdicts()
|
|
670
664
|
self._migrate_user_playbook_polarity()
|
|
665
|
+
self._migrate_lineage()
|
|
666
|
+
self._migrate_retired_at()
|
|
667
|
+
self._migrate_lineage_event_table()
|
|
668
|
+
self._migrate_playbook_optimization_candidate_metadata()
|
|
669
|
+
self._migrate_retire_profile_change_logs()
|
|
670
|
+
self._migrate_retire_playbook_aggregation_change_logs()
|
|
671
671
|
init_stall_state_table(self.conn)
|
|
672
672
|
return True
|
|
673
673
|
|
|
@@ -706,9 +706,13 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
706
706
|
# Wrap dependency + target deletes in a single critical section so
|
|
707
707
|
# concurrent writers see either both or neither.
|
|
708
708
|
with self._lock:
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
709
|
+
try:
|
|
710
|
+
self._retention_delete_dependencies(target, keys)
|
|
711
|
+
self._retention_delete_target_rows(target, keys)
|
|
712
|
+
self.conn.commit()
|
|
713
|
+
except Exception:
|
|
714
|
+
self.conn.rollback()
|
|
715
|
+
raise
|
|
712
716
|
|
|
713
717
|
def _retention_delete_dependencies(
|
|
714
718
|
self, target: RetentionTarget, keys: list[tuple[Any, ...]]
|
|
@@ -723,10 +727,14 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
723
727
|
self._delete_profile_search_rows([str(v) for v in ids])
|
|
724
728
|
elif target_name == "user_playbooks":
|
|
725
729
|
self._delete_source_windows_for_user_playbook_ids([int(v) for v in ids])
|
|
726
|
-
self._delete_playbook_search_rows(
|
|
730
|
+
self._delete_playbook_search_rows(
|
|
731
|
+
"user", [int(v) for v in ids], commit=False
|
|
732
|
+
)
|
|
727
733
|
elif target_name == "agent_playbooks":
|
|
728
734
|
self._delete_source_windows_for_agent_playbook_ids([int(v) for v in ids])
|
|
729
|
-
self._delete_playbook_search_rows(
|
|
735
|
+
self._delete_playbook_search_rows(
|
|
736
|
+
"agent", [int(v) for v in ids], commit=False
|
|
737
|
+
)
|
|
730
738
|
elif target_name == "playbook_optimization_jobs":
|
|
731
739
|
self._delete_optimizer_rows_for_job_ids([int(v) for v in ids])
|
|
732
740
|
elif target_name == "playbook_optimization_candidates":
|
|
@@ -801,29 +809,63 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
801
809
|
self._delete_in_chunks("interactions", "request_id", request_ids)
|
|
802
810
|
|
|
803
811
|
def _delete_interaction_search_rows(self, interaction_ids: list[int]) -> None:
|
|
812
|
+
"""Remove fts + vec index rows for the given interaction IDs.
|
|
813
|
+
|
|
814
|
+
Non-committing: participates in the caller's transaction. Only called
|
|
815
|
+
from inside the retention atomic block (_retention_perform_delete).
|
|
816
|
+
"""
|
|
804
817
|
if not interaction_ids:
|
|
805
818
|
return
|
|
806
819
|
self._delete_in_chunks("interactions_fts", "rowid", interaction_ids)
|
|
807
|
-
|
|
808
|
-
self.
|
|
820
|
+
if self._has_sqlite_vec:
|
|
821
|
+
self._delete_in_chunks("interactions_vec", "rowid", interaction_ids)
|
|
809
822
|
|
|
810
823
|
def _delete_profile_search_rows(self, profile_ids: list[str]) -> None:
|
|
824
|
+
"""Remove fts + vec index rows for the given profile IDs.
|
|
825
|
+
|
|
826
|
+
Non-committing: participates in the caller's transaction. Only called
|
|
827
|
+
from inside the retention atomic block (_retention_perform_delete).
|
|
828
|
+
profiles_fts is keyed by profile_id (TEXT); profiles_vec by rowid (INT).
|
|
829
|
+
"""
|
|
811
830
|
if not profile_ids:
|
|
812
831
|
return
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
832
|
+
self._delete_in_chunks("profiles_fts", "profile_id", profile_ids)
|
|
833
|
+
if self._has_sqlite_vec:
|
|
834
|
+
rows = self._select_in_chunks(
|
|
835
|
+
"SELECT rowid FROM profiles WHERE profile_id IN ({placeholders})",
|
|
836
|
+
profile_ids,
|
|
837
|
+
)
|
|
838
|
+
rowids = [row["rowid"] for row in rows]
|
|
839
|
+
if rowids:
|
|
840
|
+
self._delete_in_chunks("profiles_vec", "rowid", rowids)
|
|
820
841
|
|
|
821
|
-
def _delete_playbook_search_rows(
|
|
842
|
+
def _delete_playbook_search_rows(
|
|
843
|
+
self, kind: str, ids: list[int], *, commit: bool = True
|
|
844
|
+
) -> None:
|
|
845
|
+
"""Remove fts + vec index rows for the given playbook IDs.
|
|
846
|
+
|
|
847
|
+
Args:
|
|
848
|
+
kind: ``"user"`` or ``"agent"``.
|
|
849
|
+
ids: Playbook row IDs to remove from the search indexes.
|
|
850
|
+
commit: When ``True`` (default) commits after the deletes so the
|
|
851
|
+
after-commit callers in ``_playbook.py`` get a clean, durable
|
|
852
|
+
cleanup. Pass ``commit=False`` from inside the retention atomic
|
|
853
|
+
block so the deletes participate in the single block-level commit
|
|
854
|
+
(``_retention_perform_delete``).
|
|
855
|
+
|
|
856
|
+
Note: callers may already hold ``self._lock`` when calling this (the
|
|
857
|
+
``commit=False`` retention/atomic-delete call sites do). The internal
|
|
858
|
+
``with self._lock:`` re-acquire is safe ONLY because ``self._lock`` is a
|
|
859
|
+
reentrant ``threading.RLock``; a non-reentrant lock would deadlock here.
|
|
860
|
+
"""
|
|
822
861
|
if not ids:
|
|
823
862
|
return
|
|
824
|
-
self.
|
|
825
|
-
|
|
826
|
-
self.
|
|
863
|
+
with self._lock:
|
|
864
|
+
self._delete_in_chunks(f"{kind}_playbooks_fts", "rowid", ids)
|
|
865
|
+
if self._has_sqlite_vec:
|
|
866
|
+
self._delete_in_chunks(f"{kind}_playbooks_vec", "rowid", ids)
|
|
867
|
+
if commit:
|
|
868
|
+
self.conn.commit()
|
|
827
869
|
|
|
828
870
|
def _delete_source_windows_for_agent_playbook_ids(
|
|
829
871
|
self, agent_playbook_ids: list[int]
|
|
@@ -947,6 +989,14 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
947
989
|
self.conn.execute("ALTER TABLE interactions ADD COLUMN citations TEXT")
|
|
948
990
|
self.conn.commit()
|
|
949
991
|
|
|
992
|
+
if "image_encoding" not in columns:
|
|
993
|
+
logger.info("Adding image_encoding column to interactions table.")
|
|
994
|
+
with self._lock:
|
|
995
|
+
self.conn.execute(
|
|
996
|
+
"ALTER TABLE interactions ADD COLUMN image_encoding TEXT NOT NULL DEFAULT ''"
|
|
997
|
+
)
|
|
998
|
+
self.conn.commit()
|
|
999
|
+
|
|
950
1000
|
def _migrate_feedback_schema(self) -> None:
|
|
951
1001
|
"""Drop old-schema feedback/playbook tables so _DDL can recreate them.
|
|
952
1002
|
|
|
@@ -1109,6 +1159,39 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1109
1159
|
logger.info("Added expanded_terms column to %s", table)
|
|
1110
1160
|
self.conn.commit()
|
|
1111
1161
|
|
|
1162
|
+
def _migrate_tags(self) -> None:
|
|
1163
|
+
"""Add tags column if missing."""
|
|
1164
|
+
for table in ("profiles", "user_playbooks", "agent_playbooks"):
|
|
1165
|
+
cols = {
|
|
1166
|
+
row["name"]
|
|
1167
|
+
for row in self.conn.execute(f"PRAGMA table_info({table})").fetchall()
|
|
1168
|
+
}
|
|
1169
|
+
if "tags" not in cols:
|
|
1170
|
+
self.conn.execute(f"ALTER TABLE {table} ADD COLUMN tags TEXT")
|
|
1171
|
+
logger.info("Added tags column to %s", table)
|
|
1172
|
+
self.conn.commit()
|
|
1173
|
+
|
|
1174
|
+
def _migrate_profile_source_interaction_ids(self) -> None:
|
|
1175
|
+
"""Add profile source interaction ids for provenance on existing DBs."""
|
|
1176
|
+
cols = {
|
|
1177
|
+
row["name"]
|
|
1178
|
+
for row in self.conn.execute("PRAGMA table_info(profiles)").fetchall()
|
|
1179
|
+
}
|
|
1180
|
+
if "source_interaction_ids" not in cols:
|
|
1181
|
+
self.conn.execute(
|
|
1182
|
+
"ALTER TABLE profiles ADD COLUMN source_interaction_ids TEXT"
|
|
1183
|
+
)
|
|
1184
|
+
logger.info("Added source_interaction_ids column to profiles")
|
|
1185
|
+
self.conn.commit()
|
|
1186
|
+
|
|
1187
|
+
def _migrate_interaction_window_indexes(self) -> None:
|
|
1188
|
+
"""Add composite indexes used by sliding-window provenance lookups."""
|
|
1189
|
+
self.conn.execute(
|
|
1190
|
+
"CREATE INDEX IF NOT EXISTS idx_interactions_user_created_at_desc "
|
|
1191
|
+
"ON interactions(user_id, created_at DESC, interaction_id DESC)"
|
|
1192
|
+
)
|
|
1193
|
+
self.conn.commit()
|
|
1194
|
+
|
|
1112
1195
|
def _migrate_agent_runs_schema(self) -> None:
|
|
1113
1196
|
"""Add resumable-agent run columns if missing from existing SQLite DBs."""
|
|
1114
1197
|
cols = {
|
|
@@ -1178,6 +1261,194 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1178
1261
|
logger.info("Dropped legacy polarity column from user_playbooks")
|
|
1179
1262
|
self.conn.commit()
|
|
1180
1263
|
|
|
1264
|
+
def _migrate_lineage(self) -> None:
|
|
1265
|
+
"""Add merged_into/superseded_by forward-pointer columns if missing.
|
|
1266
|
+
|
|
1267
|
+
Backfill-safe: columns are nullable with no default. INTEGER for playbook
|
|
1268
|
+
tables (int foreign-key pointers), TEXT for profiles (str profile_id pointers).
|
|
1269
|
+
"""
|
|
1270
|
+
int_tables = {"user_playbooks": "INTEGER", "agent_playbooks": "INTEGER"}
|
|
1271
|
+
str_tables = {"profiles": "TEXT"}
|
|
1272
|
+
for table, coltype in {**int_tables, **str_tables}.items():
|
|
1273
|
+
cols = {
|
|
1274
|
+
row["name"]
|
|
1275
|
+
for row in self.conn.execute(f"PRAGMA table_info({table})").fetchall()
|
|
1276
|
+
}
|
|
1277
|
+
for col in ("merged_into", "superseded_by"):
|
|
1278
|
+
if col not in cols:
|
|
1279
|
+
self.conn.execute(
|
|
1280
|
+
f"ALTER TABLE {table} ADD COLUMN {col} {coltype}" # noqa: S608
|
|
1281
|
+
)
|
|
1282
|
+
logger.info("Added %s column to %s", col, table)
|
|
1283
|
+
self.conn.commit()
|
|
1284
|
+
|
|
1285
|
+
def _migrate_retired_at(self) -> None:
|
|
1286
|
+
"""Add nullable ``retired_at INTEGER`` GC column to tombstone-bearing tables.
|
|
1287
|
+
|
|
1288
|
+
Backfill-safe: column is nullable with no default. Existing tombstones
|
|
1289
|
+
will have ``retired_at = NULL`` (conservative — GC T2 uses ``retired_at``
|
|
1290
|
+
as the age signal, so old tombstones without it are simply not yet eligible
|
|
1291
|
+
by the new clock; ops can backfill via T4 if needed).
|
|
1292
|
+
Also creates the covering index for GC queries (idempotent).
|
|
1293
|
+
"""
|
|
1294
|
+
with self._lock:
|
|
1295
|
+
for table in ("profiles", "user_playbooks", "agent_playbooks"):
|
|
1296
|
+
cols = {
|
|
1297
|
+
row["name"]
|
|
1298
|
+
for row in self.conn.execute(
|
|
1299
|
+
f"PRAGMA table_info({table})" # noqa: S608
|
|
1300
|
+
).fetchall()
|
|
1301
|
+
}
|
|
1302
|
+
if "retired_at" not in cols:
|
|
1303
|
+
self.conn.execute(
|
|
1304
|
+
f"ALTER TABLE {table} ADD COLUMN retired_at INTEGER" # noqa: S608
|
|
1305
|
+
)
|
|
1306
|
+
logger.info("Added retired_at column to %s", table)
|
|
1307
|
+
self.conn.execute(
|
|
1308
|
+
f"CREATE INDEX IF NOT EXISTS idx_{table}_retired_at " # noqa: S608
|
|
1309
|
+
f"ON {table}(status, retired_at)"
|
|
1310
|
+
)
|
|
1311
|
+
self.conn.commit()
|
|
1312
|
+
|
|
1313
|
+
def _migrate_lineage_event_table(self) -> None:
|
|
1314
|
+
"""Create the lineage_event table + index for existing databases (idempotent)."""
|
|
1315
|
+
with self._lock:
|
|
1316
|
+
self.conn.executescript("""
|
|
1317
|
+
CREATE TABLE IF NOT EXISTS lineage_event (
|
|
1318
|
+
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1319
|
+
org_id TEXT NOT NULL,
|
|
1320
|
+
entity_type TEXT NOT NULL,
|
|
1321
|
+
entity_id TEXT NOT NULL,
|
|
1322
|
+
op TEXT NOT NULL,
|
|
1323
|
+
prov_relation TEXT NOT NULL DEFAULT '',
|
|
1324
|
+
source_ids TEXT NOT NULL DEFAULT '[]',
|
|
1325
|
+
actor TEXT NOT NULL DEFAULT '',
|
|
1326
|
+
request_id TEXT NOT NULL DEFAULT '',
|
|
1327
|
+
reason TEXT NOT NULL DEFAULT '',
|
|
1328
|
+
created_at INTEGER NOT NULL,
|
|
1329
|
+
UNIQUE (org_id, entity_type, entity_id, op, request_id)
|
|
1330
|
+
);
|
|
1331
|
+
CREATE INDEX IF NOT EXISTS idx_lineage_entity
|
|
1332
|
+
ON lineage_event (entity_type, entity_id);
|
|
1333
|
+
""")
|
|
1334
|
+
existing_cols = {
|
|
1335
|
+
row["name"]
|
|
1336
|
+
for row in self.conn.execute(
|
|
1337
|
+
"PRAGMA table_info(lineage_event)"
|
|
1338
|
+
).fetchall()
|
|
1339
|
+
}
|
|
1340
|
+
for col in ("from_status", "to_status", "status_namespace"):
|
|
1341
|
+
if col not in existing_cols:
|
|
1342
|
+
self.conn.execute(
|
|
1343
|
+
f"ALTER TABLE lineage_event ADD COLUMN {col} TEXT" # noqa: S608
|
|
1344
|
+
)
|
|
1345
|
+
logger.info("Added %s column to lineage_event", col)
|
|
1346
|
+
self.conn.commit()
|
|
1347
|
+
|
|
1348
|
+
def _migrate_playbook_optimization_candidate_metadata(self) -> None:
|
|
1349
|
+
"""Add metadata_json to legacy optimizer candidate tables when missing."""
|
|
1350
|
+
cols = {
|
|
1351
|
+
row["name"]
|
|
1352
|
+
for row in self.conn.execute(
|
|
1353
|
+
"PRAGMA table_info(playbook_optimization_candidates)"
|
|
1354
|
+
).fetchall()
|
|
1355
|
+
}
|
|
1356
|
+
if not cols:
|
|
1357
|
+
return
|
|
1358
|
+
if "metadata_json" not in cols:
|
|
1359
|
+
self.conn.execute(
|
|
1360
|
+
"ALTER TABLE playbook_optimization_candidates "
|
|
1361
|
+
"ADD COLUMN metadata_json TEXT NOT NULL DEFAULT '{}'"
|
|
1362
|
+
)
|
|
1363
|
+
logger.info(
|
|
1364
|
+
"Added metadata_json column to playbook_optimization_candidates"
|
|
1365
|
+
)
|
|
1366
|
+
self.conn.commit()
|
|
1367
|
+
|
|
1368
|
+
def _migrate_retire_profile_change_logs(self) -> None:
|
|
1369
|
+
"""Retire the frozen ``profile_change_logs`` table via a reversible RENAME.
|
|
1370
|
+
|
|
1371
|
+
Lineage B3 Task 8: the legacy change log is fully de-referenced (no
|
|
1372
|
+
readers, writers, or GDPR delete callers remain) and the view is served
|
|
1373
|
+
from reconstruction. We rename the table out of the way now and DROP it
|
|
1374
|
+
in a later migration after the recovery window — keeping the data
|
|
1375
|
+
recoverable in the interim.
|
|
1376
|
+
|
|
1377
|
+
Idempotent: SQLite has no ``RENAME ... IF EXISTS``, so we guard on the
|
|
1378
|
+
source table's presence and no-op once it has been renamed. The
|
|
1379
|
+
``CREATE TABLE`` for ``profile_change_logs`` was deleted from ``_DDL`` so
|
|
1380
|
+
``executescript(_DDL)`` does not recreate an empty table after the rename.
|
|
1381
|
+
"""
|
|
1382
|
+
with self._lock:
|
|
1383
|
+
row = self.conn.execute(
|
|
1384
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1385
|
+
("profile_change_logs",),
|
|
1386
|
+
).fetchone()
|
|
1387
|
+
if row is None:
|
|
1388
|
+
return
|
|
1389
|
+
target_row = self.conn.execute(
|
|
1390
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1391
|
+
("profile_change_logs_retired_20260623",),
|
|
1392
|
+
).fetchone()
|
|
1393
|
+
if target_row is not None:
|
|
1394
|
+
logger.info(
|
|
1395
|
+
"Retired target profile_change_logs_retired_20260623 already exists;"
|
|
1396
|
+
" skipping rename (idempotent no-op)."
|
|
1397
|
+
)
|
|
1398
|
+
return
|
|
1399
|
+
self.conn.execute(
|
|
1400
|
+
"ALTER TABLE profile_change_logs "
|
|
1401
|
+
"RENAME TO profile_change_logs_retired_20260623"
|
|
1402
|
+
)
|
|
1403
|
+
logger.info(
|
|
1404
|
+
"Renamed profile_change_logs to "
|
|
1405
|
+
"profile_change_logs_retired_20260623 (B3 Task 8 retirement)"
|
|
1406
|
+
)
|
|
1407
|
+
self.conn.commit()
|
|
1408
|
+
|
|
1409
|
+
def _migrate_retire_playbook_aggregation_change_logs(self) -> None:
|
|
1410
|
+
"""Retire the frozen ``playbook_aggregation_change_logs`` table via a reversible RENAME.
|
|
1411
|
+
|
|
1412
|
+
Lineage Track B Task 4: the legacy change log is fully de-referenced (no
|
|
1413
|
+
readers, writers, or GDPR delete callers remain) and the view is served
|
|
1414
|
+
from reconstruction. We rename the table out of the way now and DROP it
|
|
1415
|
+
in a later migration after the recovery window — keeping the data
|
|
1416
|
+
recoverable in the interim.
|
|
1417
|
+
|
|
1418
|
+
Idempotent: SQLite has no ``RENAME ... IF EXISTS``, so we guard on the
|
|
1419
|
+
source table's presence and no-op once it has been renamed. The
|
|
1420
|
+
``CREATE TABLE`` for ``playbook_aggregation_change_logs`` was deleted from
|
|
1421
|
+
``_DDL`` so ``executescript(_DDL)`` does not recreate an empty table after
|
|
1422
|
+
the rename.
|
|
1423
|
+
"""
|
|
1424
|
+
with self._lock:
|
|
1425
|
+
row = self.conn.execute(
|
|
1426
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1427
|
+
("playbook_aggregation_change_logs",),
|
|
1428
|
+
).fetchone()
|
|
1429
|
+
if row is None:
|
|
1430
|
+
return
|
|
1431
|
+
target_row = self.conn.execute(
|
|
1432
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1433
|
+
("playbook_aggregation_change_logs_retired_20260624",),
|
|
1434
|
+
).fetchone()
|
|
1435
|
+
if target_row is not None:
|
|
1436
|
+
logger.info(
|
|
1437
|
+
"Retired target playbook_aggregation_change_logs_retired_20260624"
|
|
1438
|
+
" already exists; skipping rename (idempotent no-op)."
|
|
1439
|
+
)
|
|
1440
|
+
return
|
|
1441
|
+
self.conn.execute(
|
|
1442
|
+
"ALTER TABLE playbook_aggregation_change_logs "
|
|
1443
|
+
"RENAME TO playbook_aggregation_change_logs_retired_20260624"
|
|
1444
|
+
)
|
|
1445
|
+
logger.info(
|
|
1446
|
+
"Renamed playbook_aggregation_change_logs to "
|
|
1447
|
+
"playbook_aggregation_change_logs_retired_20260624 "
|
|
1448
|
+
"(Track B Task 4 retirement)"
|
|
1449
|
+
)
|
|
1450
|
+
self.conn.commit()
|
|
1451
|
+
|
|
1181
1452
|
def _migrate_agent_playbook_source_windows(self) -> None:
|
|
1182
1453
|
"""Add source window snapshots to existing agent source mappings."""
|
|
1183
1454
|
cols = {
|
|
@@ -1203,24 +1474,122 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1203
1474
|
)
|
|
1204
1475
|
self.conn.commit()
|
|
1205
1476
|
|
|
1206
|
-
def
|
|
1207
|
-
"""Add
|
|
1477
|
+
def _migrate_request_evaluation_only(self) -> None:
|
|
1478
|
+
"""Add evaluation_only column to requests for learning exclusion."""
|
|
1479
|
+
cols = {
|
|
1480
|
+
row["name"]
|
|
1481
|
+
for row in self.conn.execute("PRAGMA table_info(requests)").fetchall()
|
|
1482
|
+
}
|
|
1483
|
+
if not cols:
|
|
1484
|
+
return
|
|
1485
|
+
if "evaluation_only" not in cols:
|
|
1486
|
+
self.conn.execute(
|
|
1487
|
+
"ALTER TABLE requests ADD COLUMN evaluation_only INTEGER NOT NULL DEFAULT 0"
|
|
1488
|
+
)
|
|
1489
|
+
logger.info("Added evaluation_only column to requests")
|
|
1490
|
+
self.conn.commit()
|
|
1208
1491
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1492
|
+
def _migrate_request_session_id_required(self) -> None:
|
|
1493
|
+
"""Require non-empty session ids on ``requests``.
|
|
1494
|
+
|
|
1495
|
+
SQLite cannot add a ``NOT NULL`` or ``CHECK`` constraint to an
|
|
1496
|
+
existing column, so existing databases are rebuilt in place. Historical
|
|
1497
|
+
null/blank sessions are intentionally backfilled per request to avoid
|
|
1498
|
+
inventing conversation groupings that were never recorded.
|
|
1212
1499
|
"""
|
|
1213
1500
|
cols = {
|
|
1214
|
-
row["name"]
|
|
1501
|
+
row["name"]: row
|
|
1215
1502
|
for row in self.conn.execute("PRAGMA table_info(requests)").fetchall()
|
|
1216
1503
|
}
|
|
1504
|
+
if not cols or "session_id" not in cols:
|
|
1505
|
+
return
|
|
1506
|
+
|
|
1507
|
+
table_sql_row = self.conn.execute(
|
|
1508
|
+
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'requests'"
|
|
1509
|
+
).fetchone()
|
|
1510
|
+
table_sql = (table_sql_row["sql"] if table_sql_row else "") or ""
|
|
1511
|
+
blank_count = self.conn.execute(
|
|
1512
|
+
"SELECT COUNT(*) FROM requests WHERE session_id IS NULL OR trim(session_id) = ''"
|
|
1513
|
+
).fetchone()[0]
|
|
1514
|
+
has_required_schema = (
|
|
1515
|
+
bool(cols["session_id"]["notnull"])
|
|
1516
|
+
and "CHECK (trim(session_id) != '')" in table_sql
|
|
1517
|
+
)
|
|
1518
|
+
if has_required_schema and blank_count == 0:
|
|
1519
|
+
return
|
|
1520
|
+
|
|
1521
|
+
evaluation_only_expr = (
|
|
1522
|
+
"COALESCE(evaluation_only, 0)" if "evaluation_only" in cols else "0"
|
|
1523
|
+
)
|
|
1524
|
+
# NOTE: this rebuild hardcodes the full `requests` column set. If a
|
|
1525
|
+
# future migration adds a column to `requests`, it MUST be added here
|
|
1526
|
+
# too (and to the SELECT below) or the rebuild will silently drop it.
|
|
1527
|
+
self.conn.executescript(
|
|
1528
|
+
f"""
|
|
1529
|
+
CREATE TABLE requests_new (
|
|
1530
|
+
request_id TEXT PRIMARY KEY,
|
|
1531
|
+
user_id TEXT NOT NULL,
|
|
1532
|
+
created_at TEXT NOT NULL,
|
|
1533
|
+
source TEXT NOT NULL DEFAULT '',
|
|
1534
|
+
agent_version TEXT NOT NULL DEFAULT '',
|
|
1535
|
+
session_id TEXT NOT NULL CHECK (trim(session_id) != ''),
|
|
1536
|
+
evaluation_only INTEGER NOT NULL DEFAULT 0
|
|
1537
|
+
);
|
|
1538
|
+
INSERT INTO requests_new
|
|
1539
|
+
(
|
|
1540
|
+
request_id,
|
|
1541
|
+
user_id,
|
|
1542
|
+
created_at,
|
|
1543
|
+
source,
|
|
1544
|
+
agent_version,
|
|
1545
|
+
session_id,
|
|
1546
|
+
evaluation_only
|
|
1547
|
+
)
|
|
1548
|
+
SELECT
|
|
1549
|
+
request_id,
|
|
1550
|
+
user_id,
|
|
1551
|
+
created_at,
|
|
1552
|
+
COALESCE(source, ''),
|
|
1553
|
+
COALESCE(agent_version, ''),
|
|
1554
|
+
CASE
|
|
1555
|
+
WHEN session_id IS NULL OR trim(session_id) = ''
|
|
1556
|
+
THEN 'legacy-' || lower(hex(randomblob(16)))
|
|
1557
|
+
ELSE trim(session_id)
|
|
1558
|
+
END,
|
|
1559
|
+
{evaluation_only_expr}
|
|
1560
|
+
FROM requests;
|
|
1561
|
+
DROP TABLE requests;
|
|
1562
|
+
ALTER TABLE requests_new RENAME TO requests;
|
|
1563
|
+
CREATE INDEX IF NOT EXISTS idx_requests_user_id ON requests(user_id);
|
|
1564
|
+
CREATE INDEX IF NOT EXISTS idx_requests_session_id ON requests(session_id);
|
|
1565
|
+
CREATE INDEX IF NOT EXISTS idx_requests_created_at ON requests(created_at);
|
|
1566
|
+
"""
|
|
1567
|
+
)
|
|
1568
|
+
self.conn.commit()
|
|
1569
|
+
logger.info("Migrated requests.session_id to required non-empty values")
|
|
1570
|
+
|
|
1571
|
+
def _migrate_eval_result_user_id(self) -> None:
|
|
1572
|
+
"""Add user_id to session evaluation results for per-user identity."""
|
|
1573
|
+
cols = {
|
|
1574
|
+
row["name"]
|
|
1575
|
+
for row in self.conn.execute(
|
|
1576
|
+
"PRAGMA table_info(agent_success_evaluation_result)"
|
|
1577
|
+
).fetchall()
|
|
1578
|
+
}
|
|
1217
1579
|
if not cols:
|
|
1218
1580
|
return
|
|
1219
|
-
if "
|
|
1581
|
+
if "user_id" not in cols:
|
|
1220
1582
|
self.conn.execute(
|
|
1221
|
-
"ALTER TABLE
|
|
1583
|
+
"ALTER TABLE agent_success_evaluation_result "
|
|
1584
|
+
"ADD COLUMN user_id TEXT NOT NULL DEFAULT ''"
|
|
1222
1585
|
)
|
|
1223
|
-
logger.info("Added
|
|
1586
|
+
logger.info("Added user_id column to agent_success_evaluation_result")
|
|
1587
|
+
self.conn.execute("DROP INDEX IF EXISTS idx_eval_identity_created_at_desc")
|
|
1588
|
+
self.conn.execute(
|
|
1589
|
+
"CREATE INDEX IF NOT EXISTS idx_eval_identity_created_at_desc "
|
|
1590
|
+
"ON agent_success_evaluation_result"
|
|
1591
|
+
"(user_id, session_id, evaluation_name, agent_version, created_at DESC)"
|
|
1592
|
+
)
|
|
1224
1593
|
self.conn.commit()
|
|
1225
1594
|
|
|
1226
1595
|
def _migrate_shadow_comparison_verdicts(self) -> None:
|
|
@@ -1228,8 +1597,7 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1228
1597
|
|
|
1229
1598
|
Idempotent; safe to run on every startup. The PRAGMA-LBYL guard
|
|
1230
1599
|
avoids running the CREATE statements on every boot for
|
|
1231
|
-
already-migrated DBs
|
|
1232
|
-
pattern. The ``CREATE TABLE IF NOT EXISTS`` in :data:`_DDL` will
|
|
1600
|
+
already-migrated DBs. The ``CREATE TABLE IF NOT EXISTS`` in :data:`_DDL` will
|
|
1233
1601
|
also create this table on a fresh database, so this helper is a
|
|
1234
1602
|
no-op there; its purpose is explicit symmetry with the per-feature
|
|
1235
1603
|
migration convention and a single named hook the disk/supabase
|
|
@@ -1461,28 +1829,51 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1461
1829
|
# ------------------------------------------------------------------
|
|
1462
1830
|
|
|
1463
1831
|
def clear_user_data(self, user_id: str) -> dict[str, int]:
|
|
1464
|
-
"""
|
|
1832
|
+
"""Per-``user_id`` row deletion across all user-scoped tables.
|
|
1465
1833
|
|
|
1466
|
-
Overrides the BaseStorage default with
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
skills and have no ``user_id`` column.
|
|
1834
|
+
Overrides the BaseStorage default with an optimized SQL implementation.
|
|
1835
|
+
Removes interactions, user playbooks, profiles, and requests scoped to
|
|
1836
|
+
the user. Intentionally does NOT touch ``agent_playbooks`` — they are
|
|
1837
|
+
the cross-project rollup of skills and have no ``user_id`` column.
|
|
1471
1838
|
|
|
1472
1839
|
Also cleans up FTS and vector sidecars for the user's rows so
|
|
1473
1840
|
subsequent searches don't surface deleted data.
|
|
1474
1841
|
|
|
1842
|
+
**Lineage-aware erasure for profiles and user_playbooks:**
|
|
1843
|
+
Rows that are tombstones (``merged_into`` or ``superseded_by`` is set)
|
|
1844
|
+
*or* are pointed-to by another row (``has_inbound_lineage_refs`` returns
|
|
1845
|
+
True) are **content-purged** (skeleton kept, body blanked) rather than
|
|
1846
|
+
hard-deleted. This preserves chain resolution across user erasures.
|
|
1847
|
+
Standalone rows with no lineage involvement are hard-deleted as before.
|
|
1848
|
+
|
|
1849
|
+
The purge/delete decision delegates to
|
|
1850
|
+
``BaseStorage._partition_purge_vs_delete`` so the logic is defined once
|
|
1851
|
+
and shared with the default ``clear_user_data`` implementation used by
|
|
1852
|
+
Supabase/Postgres backends.
|
|
1853
|
+
|
|
1854
|
+
**Commit ordering (atomicity invariant):**
|
|
1855
|
+
The hard-deletes for interactions, requests, and the delete-sets of
|
|
1856
|
+
profiles/user_playbooks are committed in one transaction first. Then
|
|
1857
|
+
``purge_content`` is called for each purge-eligible row — each call
|
|
1858
|
+
commits atomically on its own. This two-phase approach is required
|
|
1859
|
+
because ``purge_content`` issues its own ``conn.commit()``, and nesting
|
|
1860
|
+
it inside the outer transaction would prematurely flush the still-pending
|
|
1861
|
+
hard-DELETEs.
|
|
1862
|
+
|
|
1475
1863
|
Args:
|
|
1476
1864
|
user_id (str): The user id whose rows should be deleted.
|
|
1477
1865
|
|
|
1478
1866
|
Returns:
|
|
1479
|
-
dict[str, int]: Per-entity
|
|
1480
|
-
``
|
|
1481
|
-
``
|
|
1867
|
+
dict[str, int]: Per-entity counts with keys ``interactions``,
|
|
1868
|
+
``user_playbooks``, ``profiles``, ``requests``,
|
|
1869
|
+
``purged_profiles``, and ``purged_user_playbooks``.
|
|
1870
|
+
``profiles`` and ``user_playbooks`` reflect hard-deleted counts;
|
|
1871
|
+
purged rows are counted separately.
|
|
1482
1872
|
"""
|
|
1483
1873
|
with self._lock:
|
|
1484
|
-
#
|
|
1485
|
-
#
|
|
1874
|
+
# ------------------------------------------------------------------
|
|
1875
|
+
# Phase 1: snapshot all user-scoped ids before any mutations.
|
|
1876
|
+
# ------------------------------------------------------------------
|
|
1486
1877
|
interaction_ids = [
|
|
1487
1878
|
r["interaction_id"]
|
|
1488
1879
|
for r in self.conn.execute(
|
|
@@ -1490,7 +1881,7 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1490
1881
|
(user_id,),
|
|
1491
1882
|
).fetchall()
|
|
1492
1883
|
]
|
|
1493
|
-
|
|
1884
|
+
raw_upb_ids = [
|
|
1494
1885
|
r["user_playbook_id"]
|
|
1495
1886
|
for r in self.conn.execute(
|
|
1496
1887
|
"SELECT user_playbook_id FROM user_playbooks WHERE user_id = ?",
|
|
@@ -1501,67 +1892,97 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1501
1892
|
"SELECT rowid, profile_id FROM profiles WHERE user_id = ?",
|
|
1502
1893
|
(user_id,),
|
|
1503
1894
|
).fetchall()
|
|
1504
|
-
profile_rowids = [r["rowid"] for r in profile_rows]
|
|
1505
|
-
profile_ids = [r["profile_id"] for r in profile_rows]
|
|
1506
1895
|
|
|
1507
|
-
# FTS cleanup
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1896
|
+
# Build a rowid lookup for FTS/vec cleanup (SQLite-specific need).
|
|
1897
|
+
profile_rowid_by_id: dict[str, int] = {
|
|
1898
|
+
r["profile_id"]: r["rowid"] for r in profile_rows
|
|
1899
|
+
}
|
|
1900
|
+
all_profile_ids = list(profile_rowid_by_id.keys())
|
|
1901
|
+
|
|
1902
|
+
# ------------------------------------------------------------------
|
|
1903
|
+
# Phase 2: partition profiles and user_playbooks into purge vs delete.
|
|
1904
|
+
# Delegates to the shared BaseStorage helper so the decision logic
|
|
1905
|
+
# is defined once and reused by all backends.
|
|
1906
|
+
# ------------------------------------------------------------------
|
|
1907
|
+
purge_profile_ids, delete_profile_ids = self._partition_purge_vs_delete(
|
|
1908
|
+
"profile", all_profile_ids
|
|
1909
|
+
)
|
|
1910
|
+
purge_upb_str_ids, delete_upb_str_ids = self._partition_purge_vs_delete(
|
|
1911
|
+
"user_playbook", [str(uid) for uid in raw_upb_ids]
|
|
1912
|
+
)
|
|
1913
|
+
purge_upb_ids = [int(s) for s in purge_upb_str_ids]
|
|
1914
|
+
delete_upb_ids = [int(s) for s in delete_upb_str_ids]
|
|
1915
|
+
|
|
1916
|
+
# Rowids for the delete-set only (purge_content handles its own cleanup).
|
|
1917
|
+
delete_profile_rowids = [
|
|
1918
|
+
profile_rowid_by_id[pid]
|
|
1919
|
+
for pid in delete_profile_ids
|
|
1920
|
+
if pid in profile_rowid_by_id
|
|
1921
|
+
]
|
|
1922
|
+
|
|
1923
|
+
# ------------------------------------------------------------------
|
|
1924
|
+
# Phase 3: FTS and vector cleanup — only for the delete-sets
|
|
1925
|
+
# (purge_content handles its own index cleanup for purged rows).
|
|
1926
|
+
# Use _delete_in_chunks to stay under SQLite's SQLITE_MAX_VARIABLE_NUMBER
|
|
1927
|
+
# limit for large user datasets.
|
|
1928
|
+
# ------------------------------------------------------------------
|
|
1929
|
+
self._delete_in_chunks("interactions_fts", "rowid", interaction_ids)
|
|
1930
|
+
self._delete_in_chunks("user_playbooks_fts", "rowid", delete_upb_ids)
|
|
1931
|
+
self._delete_in_chunks("profiles_fts", "profile_id", delete_profile_ids)
|
|
1526
1932
|
|
|
1527
|
-
# Vector index cleanup (best-effort: only if sqlite-vec loaded)
|
|
1528
1933
|
if self._has_sqlite_vec:
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
("profiles_vec", profile_rowids),
|
|
1533
|
-
)
|
|
1534
|
-
for vec_table, rowids in vec_targets:
|
|
1535
|
-
if rowids:
|
|
1536
|
-
ph = ",".join("?" for _ in rowids)
|
|
1537
|
-
self.conn.execute(
|
|
1538
|
-
f"DELETE FROM {vec_table} WHERE rowid IN ({ph})",
|
|
1539
|
-
rowids,
|
|
1540
|
-
)
|
|
1934
|
+
self._delete_in_chunks("interactions_vec", "rowid", interaction_ids)
|
|
1935
|
+
self._delete_in_chunks("user_playbooks_vec", "rowid", delete_upb_ids)
|
|
1936
|
+
self._delete_in_chunks("profiles_vec", "rowid", delete_profile_rowids)
|
|
1541
1937
|
|
|
1542
|
-
#
|
|
1543
|
-
#
|
|
1544
|
-
#
|
|
1938
|
+
# ------------------------------------------------------------------
|
|
1939
|
+
# Phase 4: hard-delete the delete-sets and all interactions/requests.
|
|
1940
|
+
# ------------------------------------------------------------------
|
|
1545
1941
|
interactions_cur = self.conn.execute(
|
|
1546
1942
|
"DELETE FROM interactions WHERE user_id = ?", (user_id,)
|
|
1547
1943
|
)
|
|
1548
|
-
user_playbooks_cur = self.conn.execute(
|
|
1549
|
-
"DELETE FROM user_playbooks WHERE user_id = ?", (user_id,)
|
|
1550
|
-
)
|
|
1551
|
-
profiles_cur = self.conn.execute(
|
|
1552
|
-
"DELETE FROM profiles WHERE user_id = ?", (user_id,)
|
|
1553
|
-
)
|
|
1554
1944
|
requests_cur = self.conn.execute(
|
|
1555
1945
|
"DELETE FROM requests WHERE user_id = ?", (user_id,)
|
|
1556
1946
|
)
|
|
1947
|
+
upb_deleted_count = 0
|
|
1948
|
+
if delete_upb_ids:
|
|
1949
|
+
# Clean up source-window join rows before deleting the parent rows.
|
|
1950
|
+
self._delete_source_windows_for_user_playbook_ids(delete_upb_ids)
|
|
1951
|
+
self._delete_in_chunks(
|
|
1952
|
+
"user_playbooks", "user_playbook_id", delete_upb_ids
|
|
1953
|
+
)
|
|
1954
|
+
# rowcount not available from _delete_in_chunks; derive from list length
|
|
1955
|
+
# (all ids came from a pre-snapshot so they exist at delete time).
|
|
1956
|
+
upb_deleted_count = len(delete_upb_ids)
|
|
1957
|
+
profile_deleted_count = 0
|
|
1958
|
+
if delete_profile_ids:
|
|
1959
|
+
self._delete_in_chunks("profiles", "profile_id", delete_profile_ids)
|
|
1960
|
+
profile_deleted_count = len(delete_profile_ids)
|
|
1961
|
+
|
|
1962
|
+
# Commit the hard-deletes before calling purge_content, because
|
|
1963
|
+
# purge_content issues its own conn.commit() and nesting it here
|
|
1964
|
+
# would prematurely flush the still-pending deletes.
|
|
1557
1965
|
self.conn.commit()
|
|
1558
1966
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1967
|
+
# Phase 5: content-purge the purge-sets WITHOUT releasing the lock,
|
|
1968
|
+
# so erase-eligible rows are never observable by another thread with
|
|
1969
|
+
# PII still intact between the hard-delete commit and the purge. Each
|
|
1970
|
+
# purge_content call self-commits; self._lock is an RLock so its
|
|
1971
|
+
# internal ``with self._lock`` re-acquires cleanly, and the commit
|
|
1972
|
+
# above already closed the outer transaction (no flush hazard).
|
|
1973
|
+
for pid in purge_profile_ids:
|
|
1974
|
+
self.purge_content(entity_type="profile", entity_id=str(pid))
|
|
1975
|
+
for upid in purge_upb_ids:
|
|
1976
|
+
self.purge_content(entity_type="user_playbook", entity_id=str(upid))
|
|
1977
|
+
|
|
1978
|
+
return {
|
|
1979
|
+
"interactions": interactions_cur.rowcount,
|
|
1980
|
+
"user_playbooks": upb_deleted_count,
|
|
1981
|
+
"profiles": profile_deleted_count,
|
|
1982
|
+
"requests": requests_cur.rowcount,
|
|
1983
|
+
"purged_profiles": len(purge_profile_ids),
|
|
1984
|
+
"purged_user_playbooks": len(purge_upb_ids),
|
|
1985
|
+
}
|
|
1565
1986
|
|
|
1566
1987
|
|
|
1567
1988
|
# ---------------------------------------------------------------------------
|
|
@@ -1583,10 +2004,15 @@ CREATE TABLE IF NOT EXISTS profiles (
|
|
|
1583
2004
|
status TEXT,
|
|
1584
2005
|
extractor_names TEXT,
|
|
1585
2006
|
expanded_terms TEXT,
|
|
2007
|
+
tags TEXT,
|
|
2008
|
+
source_interaction_ids TEXT,
|
|
1586
2009
|
source_span TEXT,
|
|
1587
2010
|
notes TEXT,
|
|
1588
2011
|
reader_angle TEXT,
|
|
1589
|
-
|
|
2012
|
+
merged_into TEXT,
|
|
2013
|
+
superseded_by TEXT,
|
|
2014
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
2015
|
+
retired_at INTEGER
|
|
1590
2016
|
);
|
|
1591
2017
|
CREATE INDEX IF NOT EXISTS idx_profiles_user_id ON profiles(user_id);
|
|
1592
2018
|
CREATE INDEX IF NOT EXISTS idx_profiles_status ON profiles(status);
|
|
@@ -1601,6 +2027,7 @@ CREATE TABLE IF NOT EXISTS interactions (
|
|
|
1601
2027
|
user_action TEXT NOT NULL DEFAULT 'none',
|
|
1602
2028
|
user_action_description TEXT NOT NULL DEFAULT '',
|
|
1603
2029
|
interacted_image_url TEXT NOT NULL DEFAULT '',
|
|
2030
|
+
image_encoding TEXT NOT NULL DEFAULT '',
|
|
1604
2031
|
shadow_content TEXT NOT NULL DEFAULT '',
|
|
1605
2032
|
expert_content TEXT NOT NULL DEFAULT '',
|
|
1606
2033
|
tools_used TEXT,
|
|
@@ -1610,6 +2037,8 @@ CREATE TABLE IF NOT EXISTS interactions (
|
|
|
1610
2037
|
CREATE INDEX IF NOT EXISTS idx_interactions_user_id ON interactions(user_id);
|
|
1611
2038
|
CREATE INDEX IF NOT EXISTS idx_interactions_request_id ON interactions(request_id);
|
|
1612
2039
|
CREATE INDEX IF NOT EXISTS idx_interactions_created_at ON interactions(created_at);
|
|
2040
|
+
CREATE INDEX IF NOT EXISTS idx_interactions_user_created_at_desc
|
|
2041
|
+
ON interactions(user_id, created_at DESC, interaction_id DESC);
|
|
1613
2042
|
|
|
1614
2043
|
CREATE TABLE IF NOT EXISTS requests (
|
|
1615
2044
|
request_id TEXT PRIMARY KEY,
|
|
@@ -1617,12 +2046,14 @@ CREATE TABLE IF NOT EXISTS requests (
|
|
|
1617
2046
|
created_at TEXT NOT NULL,
|
|
1618
2047
|
source TEXT NOT NULL DEFAULT '',
|
|
1619
2048
|
agent_version TEXT NOT NULL DEFAULT '',
|
|
1620
|
-
session_id TEXT,
|
|
1621
|
-
|
|
2049
|
+
session_id TEXT NOT NULL CHECK (trim(session_id) != ''),
|
|
2050
|
+
evaluation_only INTEGER NOT NULL DEFAULT 0
|
|
1622
2051
|
);
|
|
1623
2052
|
CREATE INDEX IF NOT EXISTS idx_requests_user_id ON requests(user_id);
|
|
1624
2053
|
CREATE INDEX IF NOT EXISTS idx_requests_session_id ON requests(session_id);
|
|
1625
2054
|
CREATE INDEX IF NOT EXISTS idx_requests_created_at ON requests(created_at);
|
|
2055
|
+
CREATE INDEX IF NOT EXISTS idx_requests_session_created_at_asc
|
|
2056
|
+
ON requests(session_id, created_at ASC, request_id ASC);
|
|
1626
2057
|
|
|
1627
2058
|
CREATE TABLE IF NOT EXISTS user_playbooks (
|
|
1628
2059
|
user_playbook_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -1640,9 +2071,13 @@ CREATE TABLE IF NOT EXISTS user_playbooks (
|
|
|
1640
2071
|
source TEXT,
|
|
1641
2072
|
embedding TEXT,
|
|
1642
2073
|
expanded_terms TEXT,
|
|
2074
|
+
tags TEXT,
|
|
1643
2075
|
source_span TEXT,
|
|
1644
2076
|
notes TEXT,
|
|
1645
|
-
reader_angle TEXT
|
|
2077
|
+
reader_angle TEXT,
|
|
2078
|
+
merged_into INTEGER,
|
|
2079
|
+
superseded_by INTEGER,
|
|
2080
|
+
retired_at INTEGER
|
|
1646
2081
|
);
|
|
1647
2082
|
CREATE INDEX IF NOT EXISTS idx_user_playbooks_playbook_name ON user_playbooks(playbook_name);
|
|
1648
2083
|
CREATE INDEX IF NOT EXISTS idx_user_playbooks_agent_version ON user_playbooks(agent_version);
|
|
@@ -1662,7 +2097,11 @@ CREATE TABLE IF NOT EXISTS agent_playbooks (
|
|
|
1662
2097
|
playbook_metadata TEXT NOT NULL DEFAULT '',
|
|
1663
2098
|
embedding TEXT,
|
|
1664
2099
|
expanded_terms TEXT,
|
|
1665
|
-
|
|
2100
|
+
tags TEXT,
|
|
2101
|
+
status TEXT,
|
|
2102
|
+
merged_into INTEGER,
|
|
2103
|
+
superseded_by INTEGER,
|
|
2104
|
+
retired_at INTEGER
|
|
1666
2105
|
);
|
|
1667
2106
|
CREATE INDEX IF NOT EXISTS idx_agent_playbooks_playbook_name ON agent_playbooks(playbook_name);
|
|
1668
2107
|
CREATE INDEX IF NOT EXISTS idx_agent_playbooks_agent_version ON agent_playbooks(agent_version);
|
|
@@ -1671,6 +2110,7 @@ CREATE INDEX IF NOT EXISTS idx_agent_playbooks_created_at ON agent_playbooks(cre
|
|
|
1671
2110
|
|
|
1672
2111
|
CREATE TABLE IF NOT EXISTS agent_success_evaluation_result (
|
|
1673
2112
|
result_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2113
|
+
user_id TEXT NOT NULL DEFAULT '',
|
|
1674
2114
|
session_id TEXT NOT NULL,
|
|
1675
2115
|
agent_version TEXT NOT NULL DEFAULT '',
|
|
1676
2116
|
evaluation_name TEXT,
|
|
@@ -1686,31 +2126,12 @@ CREATE TABLE IF NOT EXISTS agent_success_evaluation_result (
|
|
|
1686
2126
|
);
|
|
1687
2127
|
CREATE INDEX IF NOT EXISTS idx_eval_agent_version ON agent_success_evaluation_result(agent_version);
|
|
1688
2128
|
CREATE INDEX IF NOT EXISTS idx_eval_created_at ON agent_success_evaluation_result(created_at);
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
added_profiles TEXT NOT NULL DEFAULT '[]',
|
|
1696
|
-
removed_profiles TEXT NOT NULL DEFAULT '[]',
|
|
1697
|
-
mentioned_profiles TEXT NOT NULL DEFAULT '[]'
|
|
1698
|
-
);
|
|
1699
|
-
CREATE INDEX IF NOT EXISTS idx_pcl_user_id ON profile_change_logs(user_id);
|
|
1700
|
-
CREATE INDEX IF NOT EXISTS idx_pcl_created_at ON profile_change_logs(created_at);
|
|
1701
|
-
|
|
1702
|
-
CREATE TABLE IF NOT EXISTS playbook_aggregation_change_logs (
|
|
1703
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1704
|
-
created_at INTEGER NOT NULL,
|
|
1705
|
-
playbook_name TEXT NOT NULL,
|
|
1706
|
-
agent_version TEXT NOT NULL,
|
|
1707
|
-
run_mode TEXT NOT NULL,
|
|
1708
|
-
added_playbooks TEXT NOT NULL DEFAULT '[]',
|
|
1709
|
-
removed_playbooks TEXT NOT NULL DEFAULT '[]',
|
|
1710
|
-
updated_playbooks TEXT NOT NULL DEFAULT '[]'
|
|
1711
|
-
);
|
|
1712
|
-
CREATE INDEX IF NOT EXISTS idx_pacl_playbook_name ON playbook_aggregation_change_logs(playbook_name);
|
|
1713
|
-
CREATE INDEX IF NOT EXISTS idx_pacl_agent_version ON playbook_aggregation_change_logs(agent_version);
|
|
2129
|
+
CREATE INDEX IF NOT EXISTS idx_eval_created_at_desc
|
|
2130
|
+
ON agent_success_evaluation_result(created_at DESC);
|
|
2131
|
+
CREATE INDEX IF NOT EXISTS idx_eval_agent_version_created_at_desc
|
|
2132
|
+
ON agent_success_evaluation_result(agent_version, created_at DESC);
|
|
2133
|
+
CREATE INDEX IF NOT EXISTS idx_eval_identity_created_at_desc
|
|
2134
|
+
ON agent_success_evaluation_result(user_id, session_id, evaluation_name, agent_version, created_at DESC);
|
|
1714
2135
|
|
|
1715
2136
|
CREATE TABLE IF NOT EXISTS agent_playbook_source_user_playbooks (
|
|
1716
2137
|
agent_playbook_id INTEGER NOT NULL,
|
|
@@ -1745,6 +2166,7 @@ CREATE TABLE IF NOT EXISTS playbook_optimization_candidates (
|
|
|
1745
2166
|
parent_candidate_ids TEXT NOT NULL DEFAULT '[]',
|
|
1746
2167
|
aggregate_score REAL,
|
|
1747
2168
|
is_winner INTEGER NOT NULL DEFAULT 0,
|
|
2169
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
1748
2170
|
created_at INTEGER NOT NULL
|
|
1749
2171
|
);
|
|
1750
2172
|
CREATE INDEX IF NOT EXISTS idx_poc_job ON playbook_optimization_candidates(job_id);
|
|
@@ -1941,5 +2363,30 @@ CREATE INDEX IF NOT EXISTS idx_shadow_verdicts_created_at
|
|
|
1941
2363
|
ON shadow_comparison_verdicts (created_at);
|
|
1942
2364
|
CREATE INDEX IF NOT EXISTS idx_shadow_verdicts_prompt_v
|
|
1943
2365
|
ON shadow_comparison_verdicts (judge_prompt_version);
|
|
2366
|
+
CREATE INDEX IF NOT EXISTS idx_shadow_verdicts_prompt_created_at_desc
|
|
2367
|
+
ON shadow_comparison_verdicts (judge_prompt_version, created_at DESC);
|
|
2368
|
+
|
|
2369
|
+
-- ============================================================================
|
|
2370
|
+
-- Append-only, content-free lineage event log
|
|
2371
|
+
-- ============================================================================
|
|
2372
|
+
|
|
2373
|
+
CREATE TABLE IF NOT EXISTS lineage_event (
|
|
2374
|
+
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2375
|
+
org_id TEXT NOT NULL,
|
|
2376
|
+
entity_type TEXT NOT NULL,
|
|
2377
|
+
entity_id TEXT NOT NULL,
|
|
2378
|
+
op TEXT NOT NULL,
|
|
2379
|
+
prov_relation TEXT NOT NULL DEFAULT '',
|
|
2380
|
+
source_ids TEXT NOT NULL DEFAULT '[]',
|
|
2381
|
+
actor TEXT NOT NULL DEFAULT '',
|
|
2382
|
+
request_id TEXT NOT NULL DEFAULT '',
|
|
2383
|
+
reason TEXT NOT NULL DEFAULT '',
|
|
2384
|
+
created_at INTEGER NOT NULL,
|
|
2385
|
+
from_status TEXT,
|
|
2386
|
+
to_status TEXT,
|
|
2387
|
+
status_namespace TEXT,
|
|
2388
|
+
UNIQUE (org_id, entity_type, entity_id, op, request_id)
|
|
2389
|
+
);
|
|
2390
|
+
CREATE INDEX IF NOT EXISTS idx_lineage_entity ON lineage_event (entity_type, entity_id);
|
|
1944
2391
|
|
|
1945
2392
|
"""
|