ltcai 11.5.2 → 11.7.0
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/README.md +93 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +119 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +8 -6
- package/docs/ENTERPRISE.md +2 -1
- package/docs/MULTI_AGENT_RUNTIME.md +12 -5
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/REALTIME_COLLABORATION.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +3 -2
- package/docs/kg-schema.md +1 -1
- package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
- package/lattice_brain/__init__.py +42 -79
- package/lattice_brain/graph/__init__.py +9 -24
- package/lattice_brain/graph/_kg_common/__init__.py +13 -24
- package/lattice_brain/ingestion/__init__.py +19 -58
- package/lattice_brain/ingestion/pipeline.py +20 -398
- package/lattice_brain/multimodal/__init__.py +7 -18
- package/lattice_brain/multimodal/images.py +6 -264
- package/lattice_brain/multimodal/video.py +8 -247
- package/lattice_brain/runtime/__init__.py +8 -79
- package/lattice_brain/runtime/hooks.py +22 -584
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_worker_seam.py +7 -80
- package/latticeai/api/health.py +6 -20
- package/latticeai/api/local_files.py +16 -613
- package/latticeai/api/models.py +12 -87
- package/latticeai/api/search.py +17 -255
- package/latticeai/api/tools.py +69 -750
- package/latticeai/api/voice_capture.py +8 -70
- package/latticeai/api/worker_compute.py +842 -0
- package/latticeai/api/worker_seams.py +216 -0
- package/latticeai/app_factory.py +23 -232
- package/latticeai/cli/entrypoint.py +36 -249
- package/latticeai/core/agent_permission.py +16 -91
- package/latticeai/core/messages.py +89 -531
- package/latticeai/runtime/access_runtime.py +0 -14
- package/latticeai/runtime/bootstrap.py +10 -21
- package/latticeai/runtime/brain_runtime.py +35 -43
- package/latticeai/runtime/build_phases/__init__.py +21 -37
- package/latticeai/runtime/build_phases/features.py +99 -389
- package/latticeai/runtime/build_phases/foundation.py +82 -392
- package/latticeai/runtime/build_phases/web.py +91 -408
- package/latticeai/runtime/build_phases/worker_profile.py +244 -0
- package/latticeai/runtime/lifespan_runtime.py +7 -16
- package/latticeai/runtime/platform_services_runtime.py +1 -15
- package/latticeai/runtime/runtime_context.py +13 -130
- package/latticeai/runtime/security_runtime.py +10 -103
- package/latticeai/services/architecture_readiness.py +82 -43
- package/latticeai/services/model_runtime/__init__.py +2 -11
- package/latticeai/services/model_runtime/service.py +4 -28
- package/latticeai/services/p_reinforce.py +10 -261
- package/latticeai/services/product_readiness.py +31 -38
- package/latticeai/services/search_service.py +37 -795
- package/latticeai/services/tool_dispatch.py +30 -386
- package/latticeai/services/voice_capture.py +13 -107
- package/latticeai/tools/__init__.py +22 -49
- package/latticeai/tools/commands.py +0 -163
- package/latticeai/tools/computer.py +0 -39
- package/latticeai/tools/documents.py +1 -134
- package/latticeai/tools/filesystem.py +1 -247
- package/latticeai/tools/knowledge.py +1 -52
- package/latticeai/tools/local_files.py +0 -20
- package/latticeai/worker_app.py +75 -0
- package/package.json +2 -3
- package/requirements.txt +0 -5
- package/scripts/agent_eval.py +16 -28
- package/scripts/brain_quality_eval.py +20 -183
- package/scripts/bump_version.py +5 -5
- package/scripts/check_current_release_docs.mjs +7 -4
- package/scripts/check_openapi_drift.mjs +10 -0
- package/scripts/check_server_i18n.mjs +2 -18
- package/scripts/compose_openapi.py +377 -0
- package/scripts/export_openapi.py +32 -4
- package/scripts/gen_messages_catalog_fixture.py +302 -0
- package/scripts/gen_openapi_fragments.py +365 -0
- package/scripts/gen_redact_fixture.py +196 -0
- package/scripts/gen_worker_allowlist_fixture.py +115 -0
- package/scripts/generate_agent_parity_fixtures.py +33 -14
- package/scripts/openapi_route_families.json +2161 -0
- package/scripts/release_screen_claims.json +54 -0
- package/scripts/run_integration_tests.mjs +134 -29
- package/scripts/run_sidecar_e2e.mjs +104 -11
- package/scripts/wheel_smoke.py +32 -22
- package/src-tauri/Cargo.lock +375 -9
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +151 -45
- package/src-tauri/src/main.rs +18 -18
- package/src-tauri/src/topology.rs +58 -88
- package/src-tauri/tauri.conf.json +1 -2
- package/static/app/asset-manifest.json +41 -41
- package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
- package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
- package/static/app/assets/Capture-BsTokYkk.js +1 -0
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
- package/static/app/assets/System-CMHSO9qM.js +1 -0
- package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
- package/static/app/assets/circle-check-DruOxB-4.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
- package/static/app/assets/index-D9x-kSNy.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
- package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
- package/static/app/assets/search-CT9aho2j.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/archive.py +0 -522
- package/lattice_brain/context.py +0 -325
- package/lattice_brain/conversations.py +0 -382
- package/lattice_brain/core.py +0 -82
- package/lattice_brain/graph/_kg_contract.py +0 -249
- package/lattice_brain/graph/curator.py +0 -676
- package/lattice_brain/graph/discovery.py +0 -595
- package/lattice_brain/graph/discovery_index/__init__.py +0 -35
- package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
- package/lattice_brain/graph/discovery_index/extract.py +0 -137
- package/lattice_brain/graph/discovery_index/scan.py +0 -411
- package/lattice_brain/graph/discovery_index/upsert.py +0 -495
- package/lattice_brain/graph/documents.py +0 -380
- package/lattice_brain/graph/fusion.py +0 -395
- package/lattice_brain/graph/identity.py +0 -175
- package/lattice_brain/graph/image_vectors.py +0 -230
- package/lattice_brain/graph/ingest.py +0 -829
- package/lattice_brain/graph/network.py +0 -205
- package/lattice_brain/graph/proactive.py +0 -724
- package/lattice_brain/graph/projection/__init__.py +0 -42
- package/lattice_brain/graph/projection/curation.py +0 -500
- package/lattice_brain/graph/projection/v2_schema.py +0 -518
- package/lattice_brain/graph/provenance.py +0 -524
- package/lattice_brain/graph/rerank.py +0 -163
- package/lattice_brain/graph/retrieval/__init__.py +0 -54
- package/lattice_brain/graph/retrieval/context.py +0 -197
- package/lattice_brain/graph/retrieval/graph_view.py +0 -319
- package/lattice_brain/graph/retrieval/hybrid.py +0 -488
- package/lattice_brain/graph/retrieval/maintenance.py +0 -121
- package/lattice_brain/graph/retrieval/signals.py +0 -95
- package/lattice_brain/graph/retrieval_docgen.py +0 -253
- package/lattice_brain/graph/retrieval_policy.py +0 -180
- package/lattice_brain/graph/retrieval_reads.py +0 -769
- package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
- package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
- package/lattice_brain/graph/retrieval_vector/search.py +0 -560
- package/lattice_brain/graph/retrieval_vector/status.py +0 -374
- package/lattice_brain/graph/schema.py +0 -792
- package/lattice_brain/graph/store.py +0 -268
- package/lattice_brain/graph/vector_index/__init__.py +0 -85
- package/lattice_brain/graph/vector_index/base.py +0 -167
- package/lattice_brain/graph/vector_index/brute_force.py +0 -110
- package/lattice_brain/graph/vector_index/hnsw.py +0 -290
- package/lattice_brain/graph/vector_index/jobs.py +0 -287
- package/lattice_brain/graph/vector_index/quantized.py +0 -148
- package/lattice_brain/graph/vector_index/selector.py +0 -161
- package/lattice_brain/graph/write_master.py +0 -308
- package/lattice_brain/ingestion/_contract.py +0 -90
- package/lattice_brain/ingestion/folder_scan.py +0 -57
- package/lattice_brain/ingestion/folders.py +0 -258
- package/lattice_brain/ingestion/jobs_api.py +0 -107
- package/lattice_brain/ingestion/routing.py +0 -295
- package/lattice_brain/ingestion_jobs.py +0 -380
- package/lattice_brain/memory.py +0 -75
- package/lattice_brain/portability/__init__.py +0 -90
- package/lattice_brain/portability/_contract.py +0 -42
- package/lattice_brain/portability/backups.py +0 -338
- package/lattice_brain/portability/bundles.py +0 -136
- package/lattice_brain/portability/constants.py +0 -93
- package/lattice_brain/portability/fsops.py +0 -138
- package/lattice_brain/portability/service.py +0 -41
- package/lattice_brain/portability/sharing.py +0 -710
- package/lattice_brain/quality.py +0 -543
- package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
- package/lattice_brain/runtime/agent_runtime.py +0 -859
- package/lattice_brain/runtime/contracts.py +0 -460
- package/lattice_brain/runtime/multi_agent.py +0 -942
- package/lattice_brain/runtime/statuses.py +0 -10
- package/lattice_brain/sealed_box.py +0 -240
- package/lattice_brain/self_model.py +0 -675
- package/lattice_brain/sensitivity.py +0 -94
- package/lattice_brain/storage/__init__.py +0 -22
- package/lattice_brain/storage/base.py +0 -100
- package/lattice_brain/storage/docker.py +0 -105
- package/lattice_brain/storage/factory.py +0 -31
- package/lattice_brain/storage/migration.py +0 -191
- package/lattice_brain/storage/postgres.py +0 -123
- package/lattice_brain/storage/sqlite.py +0 -143
- package/lattice_brain/synthesis.py +0 -824
- package/lattice_brain/workflow.py +0 -497
- package/latticeai/api/admin.py +0 -471
- package/latticeai/api/agent_registry.py +0 -105
- package/latticeai/api/agents.py +0 -228
- package/latticeai/api/auth.py +0 -383
- package/latticeai/api/automation_intelligence.py +0 -401
- package/latticeai/api/brain_intelligence.py +0 -199
- package/latticeai/api/browser.py +0 -493
- package/latticeai/api/change_proposals.py +0 -89
- package/latticeai/api/chat.py +0 -572
- package/latticeai/api/chat_agent_http.py +0 -892
- package/latticeai/api/chat_contracts.py +0 -73
- package/latticeai/api/chat_documents.py +0 -276
- package/latticeai/api/chat_helpers.py +0 -460
- package/latticeai/api/chat_history.py +0 -101
- package/latticeai/api/chat_hybrid.py +0 -113
- package/latticeai/api/chat_intents.py +0 -646
- package/latticeai/api/chat_stream.py +0 -216
- package/latticeai/api/chronicle.py +0 -63
- package/latticeai/api/command_center.py +0 -51
- package/latticeai/api/computer_use.py +0 -474
- package/latticeai/api/evidence_actions.py +0 -48
- package/latticeai/api/features.py +0 -70
- package/latticeai/api/funnel_metrics.py +0 -31
- package/latticeai/api/garden.py +0 -34
- package/latticeai/api/hooks.py +0 -165
- package/latticeai/api/index_jobs.py +0 -145
- package/latticeai/api/invitations.py +0 -100
- package/latticeai/api/knowledge_graph.py +0 -536
- package/latticeai/api/marketplace.py +0 -105
- package/latticeai/api/mcp.py +0 -482
- package/latticeai/api/memory.py +0 -270
- package/latticeai/api/network.py +0 -81
- package/latticeai/api/network_boundary.py +0 -225
- package/latticeai/api/permission_mode.py +0 -61
- package/latticeai/api/permissions.py +0 -436
- package/latticeai/api/plugins.py +0 -126
- package/latticeai/api/portability.py +0 -391
- package/latticeai/api/project_sessions.py +0 -114
- package/latticeai/api/realtime.py +0 -118
- package/latticeai/api/review_queue.py +0 -364
- package/latticeai/api/security_dashboard.py +0 -604
- package/latticeai/api/setup.py +0 -319
- package/latticeai/api/static_routes.py +0 -354
- package/latticeai/api/ui_redirects.py +0 -26
- package/latticeai/api/workflow_designer.py +0 -394
- package/latticeai/api/workspace.py +0 -856
- package/latticeai/api/workspace_scope.py +0 -125
- package/latticeai/core/agent/__init__.py +0 -93
- package/latticeai/core/agent/_contract.py +0 -79
- package/latticeai/core/agent/context.py +0 -57
- package/latticeai/core/agent/deps.py +0 -125
- package/latticeai/core/agent/execution.py +0 -622
- package/latticeai/core/agent/planning.py +0 -145
- package/latticeai/core/agent/recovery.py +0 -157
- package/latticeai/core/agent/runtime.py +0 -210
- package/latticeai/core/agent/verification.py +0 -231
- package/latticeai/core/agent_eval.py +0 -739
- package/latticeai/core/agent_helpers.py +0 -493
- package/latticeai/core/agent_profiles.py +0 -110
- package/latticeai/core/agent_prompts.py +0 -171
- package/latticeai/core/agent_registry.py +0 -232
- package/latticeai/core/agent_state.py +0 -41
- package/latticeai/core/agent_trace.py +0 -104
- package/latticeai/core/artifact_ledger.py +0 -109
- package/latticeai/core/audit.py +0 -260
- package/latticeai/core/builtin_hooks.py +0 -105
- package/latticeai/core/context_builder.py +0 -394
- package/latticeai/core/document_generator.py +0 -103
- package/latticeai/core/enterprise.py +0 -154
- package/latticeai/core/enterprise_admin.py +0 -158
- package/latticeai/core/file_generation/__init__.py +0 -115
- package/latticeai/core/file_generation/bundles.py +0 -76
- package/latticeai/core/file_generation/extraction.py +0 -154
- package/latticeai/core/file_generation/inference.py +0 -235
- package/latticeai/core/file_generation/orchestration.py +0 -152
- package/latticeai/core/file_generation/prompting.py +0 -117
- package/latticeai/core/file_generation/repair.py +0 -114
- package/latticeai/core/file_generation/sanitize.py +0 -61
- package/latticeai/core/file_generation/validation.py +0 -201
- package/latticeai/core/invitations.py +0 -132
- package/latticeai/core/legacy_compatibility.py +0 -243
- package/latticeai/core/logging_safety.py +0 -46
- package/latticeai/core/marketplace.py +0 -293
- package/latticeai/core/mcp_catalog.py +0 -452
- package/latticeai/core/mcp_registry.py +0 -506
- package/latticeai/core/network_boundary.py +0 -168
- package/latticeai/core/oidc.py +0 -208
- package/latticeai/core/plugins.py +0 -432
- package/latticeai/core/product_hardening.py +0 -218
- package/latticeai/core/project_sessions.py +0 -337
- package/latticeai/core/realtime.py +0 -238
- package/latticeai/core/run_explain.py +0 -426
- package/latticeai/core/run_store.py +0 -252
- package/latticeai/core/timezones.py +0 -80
- package/latticeai/core/workspace_computer_memory.py +0 -84
- package/latticeai/core/workspace_graph_trace.py +0 -155
- package/latticeai/core/workspace_indexing.py +0 -102
- package/latticeai/core/workspace_memory.py +0 -77
- package/latticeai/core/workspace_onboarding.py +0 -104
- package/latticeai/core/workspace_os.py +0 -978
- package/latticeai/core/workspace_os_constants.py +0 -126
- package/latticeai/core/workspace_os_state.py +0 -180
- package/latticeai/core/workspace_os_utils.py +0 -103
- package/latticeai/core/workspace_permissions.py +0 -101
- package/latticeai/core/workspace_plugins.py +0 -97
- package/latticeai/core/workspace_relationships.py +0 -99
- package/latticeai/core/workspace_reorganization.py +0 -335
- package/latticeai/core/workspace_review_items.py +0 -112
- package/latticeai/core/workspace_runs.py +0 -726
- package/latticeai/core/workspace_skills.py +0 -109
- package/latticeai/core/workspace_snapshots.py +0 -198
- package/latticeai/core/workspace_timeline.py +0 -110
- package/latticeai/integrations/__init__.py +0 -0
- package/latticeai/integrations/telegram_bot/__init__.py +0 -123
- package/latticeai/integrations/telegram_bot/__main__.py +0 -17
- package/latticeai/integrations/telegram_bot/config.py +0 -86
- package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
- package/latticeai/integrations/telegram_bot/flows.py +0 -478
- package/latticeai/integrations/telegram_bot/helpers.py +0 -322
- package/latticeai/integrations/telegram_bot/screens.py +0 -394
- package/latticeai/runtime/audit_runtime.py +0 -76
- package/latticeai/runtime/automation_runtime.py +0 -81
- package/latticeai/runtime/chat_wiring.py +0 -141
- package/latticeai/runtime/context_runtime.py +0 -66
- package/latticeai/runtime/feature_toggle_wiring.py +0 -157
- package/latticeai/runtime/history_runtime.py +0 -163
- package/latticeai/runtime/history_writer.py +0 -138
- package/latticeai/runtime/hooks_runtime.py +0 -77
- package/latticeai/runtime/model_wiring.py +0 -68
- package/latticeai/runtime/namespace_runtime.py +0 -163
- package/latticeai/runtime/network_boundary_wiring.py +0 -117
- package/latticeai/runtime/network_config_runtime.py +0 -56
- package/latticeai/runtime/permission_mode_wiring.py +0 -112
- package/latticeai/runtime/persistence_runtime.py +0 -159
- package/latticeai/runtime/platform_runtime_wiring.py +0 -89
- package/latticeai/runtime/review_wiring.py +0 -42
- package/latticeai/runtime/router_registration.py +0 -693
- package/latticeai/runtime/service_singletons.py +0 -55
- package/latticeai/runtime/sso_config_runtime.py +0 -128
- package/latticeai/runtime/user_key_runtime.py +0 -106
- package/latticeai/runtime/web_runtime.py +0 -92
- package/latticeai/server_app.py +0 -51
- package/latticeai/services/app_context.py +0 -130
- package/latticeai/services/automation_execution.py +0 -266
- package/latticeai/services/automation_intelligence.py +0 -614
- package/latticeai/services/brain_automation.py +0 -191
- package/latticeai/services/brain_intelligence/__init__.py +0 -58
- package/latticeai/services/brain_intelligence/_contract.py +0 -71
- package/latticeai/services/brain_intelligence/consistency.py +0 -193
- package/latticeai/services/brain_intelligence/constants.py +0 -47
- package/latticeai/services/brain_intelligence/digest.py +0 -258
- package/latticeai/services/brain_intelligence/health.py +0 -331
- package/latticeai/services/brain_intelligence/proposals.py +0 -259
- package/latticeai/services/brain_intelligence/sampling.py +0 -84
- package/latticeai/services/brain_intelligence/service.py +0 -48
- package/latticeai/services/change_proposals.py +0 -471
- package/latticeai/services/chat_service.py +0 -243
- package/latticeai/services/chronicle.py +0 -555
- package/latticeai/services/cloud_egress_audit.py +0 -85
- package/latticeai/services/cloud_extraction.py +0 -129
- package/latticeai/services/cloud_streaming.py +0 -268
- package/latticeai/services/cloud_token_guard.py +0 -84
- package/latticeai/services/command_center.py +0 -548
- package/latticeai/services/evidence_actions.py +0 -258
- package/latticeai/services/feature_toggles.py +0 -502
- package/latticeai/services/folder_watch.py +0 -520
- package/latticeai/services/funnel_metrics.py +0 -307
- package/latticeai/services/hybrid_chat.py +0 -316
- package/latticeai/services/hybrid_context.py +0 -228
- package/latticeai/services/hybrid_policy.py +0 -129
- package/latticeai/services/interop_bridges.py +0 -978
- package/latticeai/services/local_knowledge.py +0 -465
- package/latticeai/services/memory_service/__init__.py +0 -52
- package/latticeai/services/memory_service/_contract.py +0 -100
- package/latticeai/services/memory_service/brief.py +0 -431
- package/latticeai/services/memory_service/constants.py +0 -57
- package/latticeai/services/memory_service/maintenance.py +0 -138
- package/latticeai/services/memory_service/manager.py +0 -186
- package/latticeai/services/memory_service/proof.py +0 -136
- package/latticeai/services/memory_service/recall.py +0 -225
- package/latticeai/services/memory_service/service.py +0 -48
- package/latticeai/services/memory_service/stores.py +0 -110
- package/latticeai/services/mode_store.py +0 -132
- package/latticeai/services/model_recommendation.py +0 -224
- package/latticeai/services/model_runtime/cloud.py +0 -87
- package/latticeai/services/network_boundary_service.py +0 -117
- package/latticeai/services/obsidian_bridge.py +0 -609
- package/latticeai/services/openai_compatible_adapter.py +0 -101
- package/latticeai/services/permission_mode_service.py +0 -122
- package/latticeai/services/platform_runtime.py +0 -366
- package/latticeai/services/review_queue.py +0 -380
- package/latticeai/services/router_context.py +0 -59
- package/latticeai/services/run_executor.py +0 -387
- package/latticeai/services/self_model_service.py +0 -171
- package/latticeai/services/setup_detection.py +0 -147
- package/latticeai/services/triggers.py +0 -378
- package/latticeai/services/upload_service.py +0 -172
- package/latticeai/services/workspace_service.py +0 -165
- package/latticeai/setup/__init__.py +0 -25
- package/latticeai/setup/auto_setup.py +0 -846
- package/latticeai/setup/demo_corpus.py +0 -98
- package/latticeai/setup/wizard/__init__.py +0 -126
- package/latticeai/setup/wizard/catalog.py +0 -175
- package/latticeai/setup/wizard/detect.py +0 -302
- package/latticeai/setup/wizard/install.py +0 -348
- package/latticeai/setup/wizard/paths.py +0 -165
- package/latticeai/setup/wizard/plans.py +0 -74
- package/latticeai/setup/wizard/recommend.py +0 -320
- package/scripts/bench_agent_smoke.py +0 -409
- package/scripts/bench_models.py +0 -540
- package/scripts/bench_vector_index.py +0 -295
- package/scripts/funnel_soft_gate.py +0 -192
- package/scripts/generate_agent_loop_fixtures.py +0 -994
- package/scripts/generate_rust_parity_fixtures.py +0 -908
- package/scripts/migrate_brain_storage.py +0 -57
- package/scripts/parity_fixture_corpus_context.py +0 -162
- package/scripts/parity_fixture_corpus_docgen.py +0 -341
- package/scripts/profile_kg.py +0 -355
- package/server.py +0 -30
- package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
- package/static/app/assets/Capture-DiQ219jW.js +0 -1
- package/static/app/assets/System-CieofHQa.js +0 -1
- package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
- package/static/app/assets/circle-check-qqLug9nU.js +0 -1
- package/static/app/assets/index-DxmOfNRi.css +0 -2
- package/static/app/assets/primitives-SNp0LRJz.js +0 -1
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"""Which local sources are never allowed to leave the machine.
|
|
2
|
-
|
|
3
|
-
This lives in Brain Core rather than in the app layer because it is a property
|
|
4
|
-
of the *data*, not of any transport: a node stamped never-leaves stays that way
|
|
5
|
-
whether it is read by the cloud path, an export, or a future surface nobody has
|
|
6
|
-
written yet. Brain Core cannot import from ``latticeai`` (see
|
|
7
|
-
``tests/unit/test_import_guard.py``), so the app-layer network boundary imports
|
|
8
|
-
this, not the other way round.
|
|
9
|
-
|
|
10
|
-
Deliberately narrow. These are locations whose *entire purpose* is to hold
|
|
11
|
-
secrets, so a match is a fact about the path rather than a guess about content.
|
|
12
|
-
A broader heuristic — scanning text for things that look like keys — would
|
|
13
|
-
produce false confidence in both directions: it would miss secrets in unusual
|
|
14
|
-
shapes and quarantine ordinary notes that merely discuss credentials.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from __future__ import annotations
|
|
18
|
-
|
|
19
|
-
from typing import Any, Optional
|
|
20
|
-
|
|
21
|
-
# Path fragments checked case-insensitively against the POSIX form of the path.
|
|
22
|
-
SENSITIVE_PATH_FRAGMENTS: tuple = (
|
|
23
|
-
"/.ssh/",
|
|
24
|
-
"/.gnupg/",
|
|
25
|
-
"/.aws/",
|
|
26
|
-
"/.kube/",
|
|
27
|
-
"/.docker/config.json",
|
|
28
|
-
"/.netrc",
|
|
29
|
-
"/.npmrc",
|
|
30
|
-
"/.pypirc",
|
|
31
|
-
"/.git-credentials",
|
|
32
|
-
"id_rsa",
|
|
33
|
-
"id_ed25519",
|
|
34
|
-
".pem",
|
|
35
|
-
".p12",
|
|
36
|
-
".pfx",
|
|
37
|
-
".keystore",
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
# Exact filenames (in any directory) that are secret-bearing by convention.
|
|
41
|
-
SENSITIVE_FILENAMES: frozenset = frozenset({
|
|
42
|
-
".env",
|
|
43
|
-
".env.local",
|
|
44
|
-
".env.production",
|
|
45
|
-
".env.development",
|
|
46
|
-
"credentials",
|
|
47
|
-
"secrets.yaml",
|
|
48
|
-
"secrets.yml",
|
|
49
|
-
"secrets.json",
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
#: Metadata key stamped on a node that must never leave the machine.
|
|
53
|
-
LOCAL_ONLY_FLAG = "local_only"
|
|
54
|
-
#: Companion key holding the human-readable reason.
|
|
55
|
-
LOCAL_ONLY_REASON = "local_only_reason"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def sensitive_reason_for_path(path: Any) -> Optional[str]:
|
|
59
|
-
"""Return why ``path`` is never-leaves, or ``None`` if it is ordinary."""
|
|
60
|
-
if not path:
|
|
61
|
-
return None
|
|
62
|
-
lowered = str(path).replace("\\", "/").lower()
|
|
63
|
-
name = lowered.rsplit("/", 1)[-1]
|
|
64
|
-
if name in SENSITIVE_FILENAMES:
|
|
65
|
-
return f"{name!r} is a secret-bearing filename"
|
|
66
|
-
for fragment in SENSITIVE_PATH_FRAGMENTS:
|
|
67
|
-
if fragment in lowered:
|
|
68
|
-
return f"path contains {fragment!r}"
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def stamp_sensitivity(metadata: dict, path: Any) -> Optional[str]:
|
|
73
|
-
"""Stamp never-leaves onto ``metadata`` when ``path`` warrants it.
|
|
74
|
-
|
|
75
|
-
Returns the reason it stamped, or ``None``. Never clears an existing flag —
|
|
76
|
-
a user or another rule may have set it for a reason this function cannot
|
|
77
|
-
see, and downgrading a never-leaves marker is not a decision code should
|
|
78
|
-
make on its own.
|
|
79
|
-
"""
|
|
80
|
-
reason = sensitive_reason_for_path(path)
|
|
81
|
-
if reason:
|
|
82
|
-
metadata[LOCAL_ONLY_FLAG] = True
|
|
83
|
-
metadata.setdefault(LOCAL_ONLY_REASON, reason)
|
|
84
|
-
return reason
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
__all__ = [
|
|
88
|
-
"SENSITIVE_PATH_FRAGMENTS",
|
|
89
|
-
"SENSITIVE_FILENAMES",
|
|
90
|
-
"LOCAL_ONLY_FLAG",
|
|
91
|
-
"LOCAL_ONLY_REASON",
|
|
92
|
-
"sensitive_reason_for_path",
|
|
93
|
-
"stamp_sensitivity",
|
|
94
|
-
]
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""Pluggable storage layer for lattice-brain."""
|
|
2
|
-
|
|
3
|
-
from .base import StorageCapabilities, StorageEngine, StorageUnavailable
|
|
4
|
-
from .docker import DockerPostgresPlan, DockerPostgresWizard
|
|
5
|
-
from .factory import storage_from_env
|
|
6
|
-
from .migration import SQLiteToPostgresMigrator, TablePlan
|
|
7
|
-
from .postgres import PostgresConfig, PostgresEngine
|
|
8
|
-
from .sqlite import SQLiteEngine
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"DockerPostgresPlan",
|
|
12
|
-
"DockerPostgresWizard",
|
|
13
|
-
"PostgresConfig",
|
|
14
|
-
"PostgresEngine",
|
|
15
|
-
"SQLiteEngine",
|
|
16
|
-
"SQLiteToPostgresMigrator",
|
|
17
|
-
"StorageCapabilities",
|
|
18
|
-
"StorageEngine",
|
|
19
|
-
"StorageUnavailable",
|
|
20
|
-
"TablePlan",
|
|
21
|
-
"storage_from_env",
|
|
22
|
-
]
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"""StorageEngine contracts for the independent Brain Core package."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from abc import ABC, abstractmethod
|
|
6
|
-
from contextlib import contextmanager
|
|
7
|
-
from dataclasses import dataclass, field
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any, Dict, Iterator, Optional
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class StorageUnavailable(RuntimeError):
|
|
13
|
-
"""Raised when an explicitly requested storage engine cannot be used."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass(frozen=True)
|
|
17
|
-
class StorageCapabilities:
|
|
18
|
-
engine: str
|
|
19
|
-
available: bool
|
|
20
|
-
reason: Optional[str] = None
|
|
21
|
-
vector_backend: str = "none"
|
|
22
|
-
vector_available: bool = False
|
|
23
|
-
backup_restore: bool = False
|
|
24
|
-
migrations: bool = False
|
|
25
|
-
encrypted_archives: bool = False
|
|
26
|
-
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
27
|
-
|
|
28
|
-
def as_dict(self) -> Dict[str, Any]:
|
|
29
|
-
return {
|
|
30
|
-
"engine": self.engine,
|
|
31
|
-
"available": self.available,
|
|
32
|
-
"reason": self.reason,
|
|
33
|
-
"vector_backend": self.vector_backend,
|
|
34
|
-
"vector_available": self.vector_available,
|
|
35
|
-
"backup_restore": self.backup_restore,
|
|
36
|
-
"migrations": self.migrations,
|
|
37
|
-
"encrypted_archives": self.encrypted_archives,
|
|
38
|
-
"metadata": self.metadata,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class StorageEngine(ABC):
|
|
43
|
-
"""Unified storage interface used by Brain Core.
|
|
44
|
-
|
|
45
|
-
The knowledge graph currently uses SQL directly, so ``connect`` is part of
|
|
46
|
-
the contract. Engines must fail loudly when unavailable; callers must not
|
|
47
|
-
silently fall back to SQLite after an explicit Postgres selection.
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
name: str
|
|
51
|
-
|
|
52
|
-
@abstractmethod
|
|
53
|
-
def capabilities(self) -> StorageCapabilities:
|
|
54
|
-
"""Return an honest capability report."""
|
|
55
|
-
|
|
56
|
-
@abstractmethod
|
|
57
|
-
def initialize(self) -> Dict[str, Any]:
|
|
58
|
-
"""Create required storage structures or raise ``StorageUnavailable``."""
|
|
59
|
-
|
|
60
|
-
@abstractmethod
|
|
61
|
-
def connect(self) -> Any:
|
|
62
|
-
"""Return a DB-API-like connection for this engine.
|
|
63
|
-
|
|
64
|
-
The caller owns the connection and must close it. Prefer
|
|
65
|
-
:meth:`session`, which does that for you — a bare ``connect()`` is only
|
|
66
|
-
correct when the statement cannot run inside a transaction (``VACUUM``)
|
|
67
|
-
or when the connection must outlive one block.
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
@contextmanager
|
|
71
|
-
def session(self) -> Iterator[Any]:
|
|
72
|
-
"""Transactional connection that is always closed.
|
|
73
|
-
|
|
74
|
-
``with sqlite3.connect(...) as conn`` commits or rolls back — it does
|
|
75
|
-
**not** close. Every site that relied on that leaked a file descriptor
|
|
76
|
-
until the connection was garbage collected, which CPython usually did
|
|
77
|
-
promptly enough to hide it. Anything holding a frame alive (a coverage
|
|
78
|
-
tracer, a profiler, a logged traceback) delays that collection and the
|
|
79
|
-
descriptors accumulate until the process hits ``EMFILE``.
|
|
80
|
-
|
|
81
|
-
This yields inside ``with conn`` so commit/rollback semantics are
|
|
82
|
-
unchanged, then closes in ``finally``.
|
|
83
|
-
"""
|
|
84
|
-
conn = self.connect()
|
|
85
|
-
try:
|
|
86
|
-
with conn:
|
|
87
|
-
yield conn
|
|
88
|
-
finally:
|
|
89
|
-
conn.close()
|
|
90
|
-
|
|
91
|
-
@abstractmethod
|
|
92
|
-
def backup(self, destination: Path) -> Dict[str, Any]:
|
|
93
|
-
"""Create a faithful engine backup at ``destination``."""
|
|
94
|
-
|
|
95
|
-
@abstractmethod
|
|
96
|
-
def restore(self, source: Path) -> Dict[str, Any]:
|
|
97
|
-
"""Restore a faithful engine backup from ``source``."""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
__all__ = ["StorageCapabilities", "StorageEngine", "StorageUnavailable"]
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"""Explicit-consent Docker setup wizard for Postgres/pgvector."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import subprocess
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Dict, List
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass(frozen=True)
|
|
12
|
-
class DockerPostgresPlan:
|
|
13
|
-
compose_path: Path
|
|
14
|
-
project_name: str
|
|
15
|
-
service_name: str = "postgres"
|
|
16
|
-
port: int = 5432
|
|
17
|
-
|
|
18
|
-
def command(self) -> List[str]:
|
|
19
|
-
return [
|
|
20
|
-
"docker",
|
|
21
|
-
"compose",
|
|
22
|
-
"-p",
|
|
23
|
-
self.project_name,
|
|
24
|
-
"-f",
|
|
25
|
-
str(self.compose_path),
|
|
26
|
-
"up",
|
|
27
|
-
"-d",
|
|
28
|
-
self.service_name,
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class DockerPostgresWizard:
|
|
33
|
-
"""Creates and starts a local Postgres container only after consent."""
|
|
34
|
-
|
|
35
|
-
def __init__(self, data_dir: Path, *, port: int = 5432) -> None:
|
|
36
|
-
self.data_dir = Path(data_dir)
|
|
37
|
-
self.port = int(port)
|
|
38
|
-
|
|
39
|
-
def write_compose(self) -> DockerPostgresPlan:
|
|
40
|
-
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
41
|
-
compose = self.data_dir / "postgres.compose.yml"
|
|
42
|
-
compose.write_text(
|
|
43
|
-
f"""services:
|
|
44
|
-
postgres:
|
|
45
|
-
image: pgvector/pgvector:pg16
|
|
46
|
-
restart: unless-stopped
|
|
47
|
-
environment:
|
|
48
|
-
POSTGRES_DB: lattice_brain
|
|
49
|
-
POSTGRES_USER: lattice
|
|
50
|
-
POSTGRES_PASSWORD: lattice-local-only
|
|
51
|
-
ports:
|
|
52
|
-
- "127.0.0.1:{self.port}:5432"
|
|
53
|
-
volumes:
|
|
54
|
-
- ./postgres-data:/var/lib/postgresql/data
|
|
55
|
-
""",
|
|
56
|
-
encoding="utf-8",
|
|
57
|
-
)
|
|
58
|
-
return DockerPostgresPlan(
|
|
59
|
-
compose_path=compose,
|
|
60
|
-
project_name="lattice-brain",
|
|
61
|
-
port=self.port,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
def start(
|
|
65
|
-
self,
|
|
66
|
-
*,
|
|
67
|
-
consent: bool,
|
|
68
|
-
dry_run: bool = False,
|
|
69
|
-
runner=subprocess.run,
|
|
70
|
-
) -> Dict[str, object]:
|
|
71
|
-
plan = self.write_compose()
|
|
72
|
-
if not consent:
|
|
73
|
-
return {
|
|
74
|
-
"status": "consent_required",
|
|
75
|
-
"started": False,
|
|
76
|
-
"compose_path": str(plan.compose_path),
|
|
77
|
-
"command": plan.command(),
|
|
78
|
-
}
|
|
79
|
-
if dry_run:
|
|
80
|
-
return {
|
|
81
|
-
"status": "dry_run",
|
|
82
|
-
"started": False,
|
|
83
|
-
"compose_path": str(plan.compose_path),
|
|
84
|
-
"command": plan.command(),
|
|
85
|
-
}
|
|
86
|
-
completed = runner(plan.command(), check=False, capture_output=True, text=True)
|
|
87
|
-
if completed.returncode != 0:
|
|
88
|
-
return {
|
|
89
|
-
"status": "failed",
|
|
90
|
-
"started": False,
|
|
91
|
-
"compose_path": str(plan.compose_path),
|
|
92
|
-
"returncode": completed.returncode,
|
|
93
|
-
"stdout": completed.stdout,
|
|
94
|
-
"stderr": completed.stderr,
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
"status": "started",
|
|
98
|
-
"started": True,
|
|
99
|
-
"compose_path": str(plan.compose_path),
|
|
100
|
-
"stdout": completed.stdout,
|
|
101
|
-
"stderr": completed.stderr,
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
__all__ = ["DockerPostgresPlan", "DockerPostgresWizard"]
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"""StorageEngine construction from environment/config values."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Mapping
|
|
7
|
-
|
|
8
|
-
from .base import StorageEngine, StorageUnavailable
|
|
9
|
-
from .postgres import PostgresEngine
|
|
10
|
-
from .sqlite import SQLiteEngine
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def storage_from_env(env: Mapping[str, str], *, data_dir: Path) -> StorageEngine:
|
|
14
|
-
engine = (env.get("LATTICEAI_STORAGE_ENGINE") or "sqlite").strip().lower()
|
|
15
|
-
if engine in {"", "sqlite"}:
|
|
16
|
-
return SQLiteEngine(Path(data_dir) / "knowledge_graph.sqlite")
|
|
17
|
-
if engine in {"postgres", "pg", "pgvector"}:
|
|
18
|
-
dsn = env.get("LATTICEAI_POSTGRES_DSN") or ""
|
|
19
|
-
if not dsn:
|
|
20
|
-
raise StorageUnavailable(
|
|
21
|
-
"LATTICEAI_STORAGE_ENGINE=postgres requires LATTICEAI_POSTGRES_DSN; "
|
|
22
|
-
"SQLite fallback is disabled for explicit Postgres selection."
|
|
23
|
-
)
|
|
24
|
-
return PostgresEngine(
|
|
25
|
-
dsn,
|
|
26
|
-
schema=env.get("LATTICEAI_POSTGRES_SCHEMA") or "lattice_brain",
|
|
27
|
-
)
|
|
28
|
-
raise StorageUnavailable(f"Unknown Brain Core storage engine: {engine}")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
__all__ = ["storage_from_env"]
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
"""Safe SQLite to Postgres migration tooling."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import sqlite3
|
|
6
|
-
from contextlib import closing
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any, Dict, List
|
|
10
|
-
|
|
11
|
-
from .postgres import PostgresEngine, _quote_ident
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _pg_type(sqlite_type: str) -> str:
|
|
15
|
-
t = str(sqlite_type or "").upper()
|
|
16
|
-
if "INT" in t:
|
|
17
|
-
return "bigint"
|
|
18
|
-
if any(token in t for token in ("REAL", "FLOA", "DOUB")):
|
|
19
|
-
return "double precision"
|
|
20
|
-
if "BLOB" in t:
|
|
21
|
-
return "bytea"
|
|
22
|
-
return "text"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _adapt_value(value: Any) -> Any:
|
|
26
|
-
if isinstance(value, memoryview):
|
|
27
|
-
return bytes(value)
|
|
28
|
-
return value
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@dataclass(frozen=True)
|
|
32
|
-
class TablePlan:
|
|
33
|
-
name: str
|
|
34
|
-
columns: List[Dict[str, str]]
|
|
35
|
-
rows: int
|
|
36
|
-
conflict_key: str
|
|
37
|
-
conflict_columns: List[str]
|
|
38
|
-
rowid_available: bool
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class SQLiteToPostgresMigrator:
|
|
42
|
-
"""Copies every user table from a Lattice SQLite brain into Postgres.
|
|
43
|
-
|
|
44
|
-
The migration is idempotent: tables with an ``id`` column upsert on ``id``;
|
|
45
|
-
tables without one use the preserved SQLite rowid in ``__source_rowid``.
|
|
46
|
-
SQLite remains untouched throughout.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
def __init__(self, sqlite_path: Path, target: PostgresEngine) -> None:
|
|
50
|
-
self.sqlite_path = Path(sqlite_path)
|
|
51
|
-
self.target = target
|
|
52
|
-
|
|
53
|
-
def plan(self) -> Dict[str, Any]:
|
|
54
|
-
if not self.sqlite_path.exists():
|
|
55
|
-
raise FileNotFoundError(f"SQLite brain database not found: {self.sqlite_path}")
|
|
56
|
-
with closing(sqlite3.connect(str(self.sqlite_path))) as conn, conn:
|
|
57
|
-
conn.row_factory = sqlite3.Row
|
|
58
|
-
table_names = [
|
|
59
|
-
row["name"]
|
|
60
|
-
for row in conn.execute(
|
|
61
|
-
"""
|
|
62
|
-
SELECT name FROM sqlite_master
|
|
63
|
-
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
|
64
|
-
ORDER BY name
|
|
65
|
-
"""
|
|
66
|
-
)
|
|
67
|
-
]
|
|
68
|
-
tables = []
|
|
69
|
-
for table in table_names:
|
|
70
|
-
cols = [
|
|
71
|
-
{"name": row["name"], "type": row["type"] or "TEXT"}
|
|
72
|
-
for row in conn.execute(f"PRAGMA table_info({_quote_sqlite_ident(table)})")
|
|
73
|
-
]
|
|
74
|
-
row_count = conn.execute(f"SELECT COUNT(*) FROM {_quote_sqlite_ident(table)}").fetchone()[0]
|
|
75
|
-
names = {c["name"] for c in cols}
|
|
76
|
-
rowid_available = _rowid_available(conn, table)
|
|
77
|
-
pk_columns = [
|
|
78
|
-
row["name"]
|
|
79
|
-
for row in sorted(
|
|
80
|
-
conn.execute(f"PRAGMA table_info({_quote_sqlite_ident(table)})"),
|
|
81
|
-
key=lambda item: int(item["pk"] or 0),
|
|
82
|
-
)
|
|
83
|
-
if int(row["pk"] or 0) > 0
|
|
84
|
-
]
|
|
85
|
-
conflict_columns = (
|
|
86
|
-
["id"]
|
|
87
|
-
if "id" in names
|
|
88
|
-
else pk_columns
|
|
89
|
-
if pk_columns
|
|
90
|
-
else ["__source_rowid"]
|
|
91
|
-
if rowid_available
|
|
92
|
-
else []
|
|
93
|
-
)
|
|
94
|
-
if not conflict_columns:
|
|
95
|
-
raise RuntimeError(
|
|
96
|
-
f"Cannot safely migrate rowid-less SQLite table without a primary key: {table}"
|
|
97
|
-
)
|
|
98
|
-
tables.append(
|
|
99
|
-
TablePlan(
|
|
100
|
-
name=table,
|
|
101
|
-
columns=cols,
|
|
102
|
-
rows=int(row_count),
|
|
103
|
-
conflict_key=conflict_columns[0],
|
|
104
|
-
conflict_columns=conflict_columns,
|
|
105
|
-
rowid_available=rowid_available,
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
return {
|
|
109
|
-
"source": str(self.sqlite_path),
|
|
110
|
-
"target_engine": self.target.name,
|
|
111
|
-
"target_schema": self.target.config.schema,
|
|
112
|
-
"tables": [table.__dict__ for table in tables],
|
|
113
|
-
"total_rows": sum(table.rows for table in tables),
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
def migrate(self, *, dry_run: bool = False) -> Dict[str, Any]:
|
|
117
|
-
plan = self.plan()
|
|
118
|
-
if dry_run:
|
|
119
|
-
return {"status": "planned", **plan}
|
|
120
|
-
schema = _quote_ident(self.target.config.schema)
|
|
121
|
-
copied: Dict[str, int] = {}
|
|
122
|
-
self.target.initialize()
|
|
123
|
-
with closing(sqlite3.connect(str(self.sqlite_path))) as src, self.target.session() as dst:
|
|
124
|
-
src.row_factory = sqlite3.Row
|
|
125
|
-
with dst.cursor() as cur:
|
|
126
|
-
for table in plan["tables"]:
|
|
127
|
-
name = str(table["name"])
|
|
128
|
-
cols = list(table["columns"])
|
|
129
|
-
conflict_columns = list(table.get("conflict_columns") or [table["conflict_key"]])
|
|
130
|
-
rowid_available = bool(table.get("rowid_available", True))
|
|
131
|
-
pg_table = f"{schema}.{_quote_ident(name)}"
|
|
132
|
-
defs = ["__source_rowid bigint NOT NULL"] if rowid_available else []
|
|
133
|
-
for col in cols:
|
|
134
|
-
defs.append(f"{_quote_ident(col['name'])} {_pg_type(col['type'])}")
|
|
135
|
-
pk = ", ".join(_quote_ident(c) for c in conflict_columns)
|
|
136
|
-
cur.execute(
|
|
137
|
-
f"CREATE TABLE IF NOT EXISTS {pg_table} ({', '.join(defs)}, PRIMARY KEY ({pk}))"
|
|
138
|
-
)
|
|
139
|
-
if rowid_available:
|
|
140
|
-
select_sql = (
|
|
141
|
-
f"SELECT rowid AS __source_rowid, * FROM {_quote_sqlite_ident(name)} ORDER BY rowid"
|
|
142
|
-
)
|
|
143
|
-
else:
|
|
144
|
-
order_by = ", ".join(_quote_sqlite_ident(c) for c in conflict_columns)
|
|
145
|
-
select_sql = f"SELECT * FROM {_quote_sqlite_ident(name)} ORDER BY {order_by}"
|
|
146
|
-
rows = src.execute(select_sql).fetchall()
|
|
147
|
-
if not rows:
|
|
148
|
-
copied[name] = 0
|
|
149
|
-
continue
|
|
150
|
-
columns = (["__source_rowid"] if rowid_available else []) + [c["name"] for c in cols]
|
|
151
|
-
placeholders = ", ".join(["%s"] * len(columns))
|
|
152
|
-
quoted_columns = ", ".join(_quote_ident(c) for c in columns)
|
|
153
|
-
updates = ", ".join(
|
|
154
|
-
f"{_quote_ident(c)} = EXCLUDED.{_quote_ident(c)}"
|
|
155
|
-
for c in columns
|
|
156
|
-
if c not in conflict_columns
|
|
157
|
-
)
|
|
158
|
-
conflict_action = f"DO UPDATE SET {updates}" if updates else "DO NOTHING"
|
|
159
|
-
sql = (
|
|
160
|
-
f"INSERT INTO {pg_table} ({quoted_columns}) VALUES ({placeholders}) "
|
|
161
|
-
f"ON CONFLICT ({pk}) {conflict_action}"
|
|
162
|
-
)
|
|
163
|
-
cur.executemany(
|
|
164
|
-
sql,
|
|
165
|
-
[
|
|
166
|
-
tuple(_adapt_value(row[col]) for col in columns)
|
|
167
|
-
for row in rows
|
|
168
|
-
],
|
|
169
|
-
)
|
|
170
|
-
copied[name] = len(rows)
|
|
171
|
-
return {
|
|
172
|
-
"status": "migrated",
|
|
173
|
-
**plan,
|
|
174
|
-
"copied_rows": copied,
|
|
175
|
-
"total_copied_rows": sum(copied.values()),
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def _quote_sqlite_ident(value: str) -> str:
|
|
180
|
-
return '"' + str(value).replace('"', '""') + '"'
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def _rowid_available(conn: sqlite3.Connection, table: str) -> bool:
|
|
184
|
-
try:
|
|
185
|
-
conn.execute(f"SELECT rowid FROM {_quote_sqlite_ident(table)} LIMIT 1").fetchall()
|
|
186
|
-
return True
|
|
187
|
-
except sqlite3.OperationalError:
|
|
188
|
-
return False
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
__all__ = ["SQLiteToPostgresMigrator", "TablePlan"]
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"""Opt-in Postgres/pgvector storage engine for Brain Core scale mode."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Any, Dict
|
|
8
|
-
|
|
9
|
-
from .base import StorageCapabilities, StorageEngine, StorageUnavailable
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _quote_ident(value: str) -> str:
|
|
13
|
-
return '"' + str(value).replace('"', '""') + '"'
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass(frozen=True)
|
|
17
|
-
class PostgresConfig:
|
|
18
|
-
dsn: str
|
|
19
|
-
schema: str = "lattice_brain"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class PostgresEngine(StorageEngine):
|
|
23
|
-
name = "postgres"
|
|
24
|
-
|
|
25
|
-
def __init__(self, dsn: str, *, schema: str = "lattice_brain") -> None:
|
|
26
|
-
self.config = PostgresConfig(dsn=dsn, schema=schema)
|
|
27
|
-
|
|
28
|
-
def _psycopg(self):
|
|
29
|
-
try:
|
|
30
|
-
import psycopg # type: ignore
|
|
31
|
-
except Exception as exc:
|
|
32
|
-
raise StorageUnavailable(
|
|
33
|
-
"Postgres storage requires optional dependency 'psycopg'. "
|
|
34
|
-
"Install the postgres extra before selecting LATTICEAI_STORAGE_ENGINE=postgres."
|
|
35
|
-
) from exc
|
|
36
|
-
return psycopg
|
|
37
|
-
|
|
38
|
-
def connect(self) -> Any:
|
|
39
|
-
if not self.config.dsn:
|
|
40
|
-
raise StorageUnavailable(
|
|
41
|
-
"Postgres storage requires LATTICEAI_POSTGRES_DSN; no SQLite fallback is attempted."
|
|
42
|
-
)
|
|
43
|
-
psycopg = self._psycopg()
|
|
44
|
-
return psycopg.connect(self.config.dsn)
|
|
45
|
-
|
|
46
|
-
def initialize(self) -> Dict[str, Any]:
|
|
47
|
-
schema = _quote_ident(self.config.schema)
|
|
48
|
-
with self.session() as conn:
|
|
49
|
-
with conn.cursor() as cur:
|
|
50
|
-
cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
|
51
|
-
cur.execute(f"CREATE SCHEMA IF NOT EXISTS {schema}")
|
|
52
|
-
cur.execute(
|
|
53
|
-
f"""
|
|
54
|
-
CREATE TABLE IF NOT EXISTS {schema}.storage_meta (
|
|
55
|
-
key text PRIMARY KEY,
|
|
56
|
-
value text NOT NULL,
|
|
57
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
58
|
-
)
|
|
59
|
-
"""
|
|
60
|
-
)
|
|
61
|
-
cur.execute(
|
|
62
|
-
f"""
|
|
63
|
-
CREATE TABLE IF NOT EXISTS {schema}.brain_vectors (
|
|
64
|
-
item_id text PRIMARY KEY,
|
|
65
|
-
item_type text NOT NULL,
|
|
66
|
-
source_node text NOT NULL,
|
|
67
|
-
text_hash text NOT NULL,
|
|
68
|
-
embedding vector,
|
|
69
|
-
embedding_dim integer NOT NULL,
|
|
70
|
-
embedding_model text NOT NULL,
|
|
71
|
-
metadata_json jsonb NOT NULL DEFAULT '{{}}'::jsonb,
|
|
72
|
-
indexed_at timestamptz NOT NULL DEFAULT now()
|
|
73
|
-
)
|
|
74
|
-
"""
|
|
75
|
-
)
|
|
76
|
-
cur.execute(
|
|
77
|
-
f"""
|
|
78
|
-
INSERT INTO {schema}.storage_meta(key, value)
|
|
79
|
-
VALUES ('engine', 'postgres')
|
|
80
|
-
ON CONFLICT (key) DO UPDATE
|
|
81
|
-
SET value = EXCLUDED.value, updated_at = now()
|
|
82
|
-
"""
|
|
83
|
-
)
|
|
84
|
-
return {"engine": self.name, "schema": self.config.schema}
|
|
85
|
-
|
|
86
|
-
def capabilities(self) -> StorageCapabilities:
|
|
87
|
-
try:
|
|
88
|
-
with self.session() as conn:
|
|
89
|
-
with conn.cursor() as cur:
|
|
90
|
-
cur.execute("SELECT extname FROM pg_extension WHERE extname='vector'")
|
|
91
|
-
pgvector = cur.fetchone() is not None
|
|
92
|
-
except Exception as exc:
|
|
93
|
-
return StorageCapabilities(
|
|
94
|
-
engine=self.name,
|
|
95
|
-
available=False,
|
|
96
|
-
reason=str(exc),
|
|
97
|
-
vector_backend="pgvector",
|
|
98
|
-
metadata={"schema": self.config.schema},
|
|
99
|
-
)
|
|
100
|
-
return StorageCapabilities(
|
|
101
|
-
engine=self.name,
|
|
102
|
-
available=True,
|
|
103
|
-
reason=None if pgvector else "pgvector extension is not installed",
|
|
104
|
-
vector_backend="pgvector",
|
|
105
|
-
vector_available=pgvector,
|
|
106
|
-
backup_restore=False,
|
|
107
|
-
migrations=True,
|
|
108
|
-
encrypted_archives=False,
|
|
109
|
-
metadata={"schema": self.config.schema},
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
def backup(self, destination: Path) -> Dict[str, Any]:
|
|
113
|
-
raise StorageUnavailable(
|
|
114
|
-
"Postgres logical backup is not implemented inside the app; use pg_dump for this engine."
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
def restore(self, source: Path) -> Dict[str, Any]:
|
|
118
|
-
raise StorageUnavailable(
|
|
119
|
-
"Postgres restore is not implemented inside the app; use pg_restore/psql for this engine."
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
__all__ = ["PostgresConfig", "PostgresEngine", "_quote_ident"]
|