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,518 +0,0 @@
|
|
|
1
|
-
"""The legacy → v2 projection: schema, backfill, per-row writes, mirrors.
|
|
2
|
-
|
|
3
|
-
The v2 tables and the ``kgv2_*`` reconstruction views are *derived* — the legacy
|
|
4
|
-
``nodes``/``edges`` tables stay authoritative — which is what makes the whole
|
|
5
|
-
DROP → CREATE → VIEWS → BACKFILL → stamp migration safe to run in one
|
|
6
|
-
transaction and simply retry on the next startup when it fails. The trigram FTS
|
|
7
|
-
index lives here too: it is the other index projected off the same rows.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
# ruff: noqa: F403,F405,S608
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
from typing import TYPE_CHECKING
|
|
14
|
-
|
|
15
|
-
from ...quiet import quiet
|
|
16
|
-
from .._kg_common import * # noqa: F401
|
|
17
|
-
|
|
18
|
-
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
19
|
-
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
20
|
-
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
21
|
-
if TYPE_CHECKING:
|
|
22
|
-
from .._kg_contract import KnowledgeGraphCore as _Core
|
|
23
|
-
else:
|
|
24
|
-
_Core = object
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class KnowledgeGraphV2SchemaMixin(_Core):
|
|
28
|
-
"""Projection + FTS. Mixed into ``KnowledgeGraphProjectionMixin``."""
|
|
29
|
-
|
|
30
|
-
_FTS_SQL = """
|
|
31
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS node_fts USING fts5(
|
|
32
|
-
node_id UNINDEXED, title, summary, metadata, tokenize='trigram'
|
|
33
|
-
);
|
|
34
|
-
CREATE TRIGGER IF NOT EXISTS node_fts_ai AFTER INSERT ON nodes BEGIN
|
|
35
|
-
INSERT INTO node_fts(node_id, title, summary, metadata)
|
|
36
|
-
VALUES (new.id, new.title, COALESCE(new.summary, ''), new.metadata_json);
|
|
37
|
-
END;
|
|
38
|
-
CREATE TRIGGER IF NOT EXISTS node_fts_au AFTER UPDATE ON nodes BEGIN
|
|
39
|
-
DELETE FROM node_fts WHERE node_id = old.id;
|
|
40
|
-
INSERT INTO node_fts(node_id, title, summary, metadata)
|
|
41
|
-
VALUES (new.id, new.title, COALESCE(new.summary, ''), new.metadata_json);
|
|
42
|
-
END;
|
|
43
|
-
CREATE TRIGGER IF NOT EXISTS node_fts_ad AFTER DELETE ON nodes BEGIN
|
|
44
|
-
DELETE FROM node_fts WHERE node_id = old.id;
|
|
45
|
-
END;
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
# ``type`` is reconstructed from ``legacy_type`` when there is one, because
|
|
49
|
-
# that column carries the label a reader expects. The trap the 11.0.1
|
|
50
|
-
# review recorded — and which 11.2.0 fixes — is that ``edges_v2.legacy_type``
|
|
51
|
-
# is ``NOT NULL DEFAULT ''``: a natively-canonical edge is written with
|
|
52
|
-
# ``legacy_type=''`` so its identity is effectively (source, target, type),
|
|
53
|
-
# and ``COALESCE('', type)`` returns ``''`` because COALESCE only skips
|
|
54
|
-
# NULL. Every canonical edge therefore read back with an **empty type**.
|
|
55
|
-
# ``NULLIF(legacy_type, '')`` is the fix: empty means "no legacy label",
|
|
56
|
-
# which is precisely what the write side meant by it.
|
|
57
|
-
#
|
|
58
|
-
# The empty string stays the write-side sentinel on purpose. SQLite treats
|
|
59
|
-
# NULLs as distinct in a UNIQUE index, so moving to NULL would silently
|
|
60
|
-
# disable the (source, target, type, legacy_type) dedupe and let the same
|
|
61
|
-
# relation land twice.
|
|
62
|
-
#
|
|
63
|
-
# The temporal columns pass through *raw* (v11.1.0): validity is not a
|
|
64
|
-
# label, and a COALESCE there would turn "still valid" (NULL) into a value.
|
|
65
|
-
# NULL in, NULL out; the fallback to ``created_at`` belongs to the read
|
|
66
|
-
# predicate (``schema.TEMPORAL_PREDICATE_SQL``), not to the view.
|
|
67
|
-
_V2_VIEWS_SQL = """
|
|
68
|
-
CREATE VIEW IF NOT EXISTS kgv2_nodes AS
|
|
69
|
-
SELECT id,
|
|
70
|
-
COALESCE(NULLIF(legacy_type, ''), type) AS type,
|
|
71
|
-
label AS title,
|
|
72
|
-
summary,
|
|
73
|
-
attrs AS metadata_json,
|
|
74
|
-
created_at, updated_at,
|
|
75
|
-
valid_from, valid_to, superseded_by
|
|
76
|
-
FROM nodes_v2;
|
|
77
|
-
CREATE VIEW IF NOT EXISTS kgv2_edges AS
|
|
78
|
-
SELECT id, source AS from_node, target AS to_node,
|
|
79
|
-
COALESCE(NULLIF(legacy_type, ''), type) AS type,
|
|
80
|
-
weight,
|
|
81
|
-
metadata AS metadata_json,
|
|
82
|
-
created_at,
|
|
83
|
-
valid_from, valid_to, superseded_by
|
|
84
|
-
FROM edges_v2;
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
def _init_fts(self) -> None:
|
|
88
|
-
self._fts_enabled = False
|
|
89
|
-
try:
|
|
90
|
-
with self._connect() as conn:
|
|
91
|
-
conn.executescript(self._FTS_SQL)
|
|
92
|
-
fts_count = conn.execute(
|
|
93
|
-
"SELECT count(*) AS c FROM node_fts"
|
|
94
|
-
).fetchone()["c"]
|
|
95
|
-
if fts_count == 0:
|
|
96
|
-
conn.execute(
|
|
97
|
-
"INSERT INTO node_fts(node_id, title, summary, metadata) "
|
|
98
|
-
"SELECT id, title, COALESCE(summary, ''), metadata_json FROM nodes"
|
|
99
|
-
)
|
|
100
|
-
self._fts_enabled = True
|
|
101
|
-
except sqlite3.OperationalError as exc:
|
|
102
|
-
# FTS5/trigram not compiled into this SQLite build. LIKE search
|
|
103
|
-
# stays authoritative; the capability is reported, never faked.
|
|
104
|
-
logging.info(
|
|
105
|
-
"FTS5 trigram index unavailable (%s); keyword search uses LIKE scans.",
|
|
106
|
-
exc,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
def _fts_match_ids(
|
|
110
|
-
self, conn: sqlite3.Connection, query: str, limit: int
|
|
111
|
-
) -> List[str]:
|
|
112
|
-
"""Ranked node ids for a trigram FTS query ('' on any failure)."""
|
|
113
|
-
if not getattr(self, "_fts_enabled", False) or len(query) < 3:
|
|
114
|
-
return []
|
|
115
|
-
escaped = query.replace('"', '""')
|
|
116
|
-
try:
|
|
117
|
-
rows = conn.execute(
|
|
118
|
-
"SELECT node_id FROM node_fts WHERE node_fts MATCH ? ORDER BY rank LIMIT ?",
|
|
119
|
-
(f'"{escaped}"', limit),
|
|
120
|
-
).fetchall()
|
|
121
|
-
except sqlite3.OperationalError:
|
|
122
|
-
return []
|
|
123
|
-
return [row["node_id"] for row in rows]
|
|
124
|
-
|
|
125
|
-
def _init_v2_schema(self) -> None:
|
|
126
|
-
"""Initialize the normalized v2 tables + reconstruction views, migrating
|
|
127
|
-
the projection layout when it is stale — **atomically**.
|
|
128
|
-
|
|
129
|
-
The entire DROP → CREATE → VIEWS → BACKFILL → version-stamp sequence runs
|
|
130
|
-
in a single transaction on one connection: on any failure it rolls back,
|
|
131
|
-
leaving the prior projection untouched and the version unchanged, so the
|
|
132
|
-
next startup simply retries. The migration only ever touches the v2
|
|
133
|
-
tables/views and the ``projection_version`` key — never the authoritative
|
|
134
|
-
legacy ``nodes``/``edges`` — so legacy data cannot be corrupted even if
|
|
135
|
-
the rebuild fails midway.
|
|
136
|
-
"""
|
|
137
|
-
if KGStoreV2 is None or _exec_script is None:
|
|
138
|
-
return
|
|
139
|
-
self._v2_projection_available = False
|
|
140
|
-
try:
|
|
141
|
-
self._backup_before_v2_flip()
|
|
142
|
-
with self._connect() as conn:
|
|
143
|
-
conn.execute("BEGIN")
|
|
144
|
-
stale = self._projection_version(conn) != _PROJECTION_VERSION
|
|
145
|
-
# Reconstruction views are non-authoritative. Recreate them on
|
|
146
|
-
# every startup so older SQLite rename migrations cannot strand
|
|
147
|
-
# a view against a temporary table such as edges_v2_old.
|
|
148
|
-
for stmt in (
|
|
149
|
-
"DROP VIEW IF EXISTS kgv2_edges",
|
|
150
|
-
"DROP VIEW IF EXISTS kgv2_nodes",
|
|
151
|
-
):
|
|
152
|
-
conn.execute(stmt)
|
|
153
|
-
if stale:
|
|
154
|
-
# The projection is non-authoritative; drop it so init_schema
|
|
155
|
-
# recreates the tables with the current normalized columns.
|
|
156
|
-
for stmt in (
|
|
157
|
-
"DROP TABLE IF EXISTS edges_v2",
|
|
158
|
-
"DROP TABLE IF EXISTS nodes_v2",
|
|
159
|
-
):
|
|
160
|
-
conn.execute(stmt)
|
|
161
|
-
# init_schema(conn=...) joins this transaction (no implicit commit)
|
|
162
|
-
KGStoreV2(self.db_path).init_schema(conn=conn)
|
|
163
|
-
_exec_script(conn, self._V2_VIEWS_SQL)
|
|
164
|
-
self._backfill_v2_on(conn, force=stale)
|
|
165
|
-
self._normalize_v2_legacy_types(conn)
|
|
166
|
-
# version stamp commits together with the backfill — never stranded
|
|
167
|
-
conn.execute(
|
|
168
|
-
"INSERT OR REPLACE INTO kg_meta(key, value) VALUES ('projection_version', ?)",
|
|
169
|
-
(str(_PROJECTION_VERSION),),
|
|
170
|
-
)
|
|
171
|
-
mastered_at = _now()
|
|
172
|
-
conn.execute(
|
|
173
|
-
"INSERT OR REPLACE INTO kg_meta(key, value) VALUES (?, ?)",
|
|
174
|
-
(_KG_DB_FORMAT_KEY, str(_KG_DB_FORMAT_VERSION)),
|
|
175
|
-
)
|
|
176
|
-
conn.execute(
|
|
177
|
-
"INSERT OR REPLACE INTO kg_meta(key, value) VALUES (?, COALESCE((SELECT value FROM kg_meta WHERE key=?), ?))",
|
|
178
|
-
(_V2_WRITE_MASTER_KEY, _V2_WRITE_MASTER_KEY, mastered_at),
|
|
179
|
-
)
|
|
180
|
-
conn.execute(f"PRAGMA user_version={_KG_DB_FORMAT_VERSION}")
|
|
181
|
-
conn.execute("SELECT 1 FROM kgv2_nodes LIMIT 1").fetchone()
|
|
182
|
-
conn.execute("SELECT 1 FROM kgv2_edges LIMIT 1").fetchone()
|
|
183
|
-
self._v2_projection_available = True
|
|
184
|
-
except Exception as e:
|
|
185
|
-
logging.warning("knowledge_graph: v2 schema init/backfill skipped: %s", e)
|
|
186
|
-
|
|
187
|
-
def _normalize_v2_legacy_types(self, conn: sqlite3.Connection) -> Dict[str, int]:
|
|
188
|
-
"""Bring pre-11.2.0 rows onto the current ``legacy_type`` convention.
|
|
189
|
-
|
|
190
|
-
The convention is: ``legacy_type`` holds the *raw* label only when it
|
|
191
|
-
differs from the canonical type, and ``''`` (edges) / ``NULL`` (nodes)
|
|
192
|
-
otherwise. Rows written by older builds could carry the canonical value
|
|
193
|
-
in both columns, which is redundant and — because the dedupe key
|
|
194
|
-
includes ``legacy_type`` — splits one relation across two rows.
|
|
195
|
-
|
|
196
|
-
Idempotent by construction: a second run matches nothing. ``UPDATE OR
|
|
197
|
-
IGNORE`` is used because collapsing a redundant row can collide with
|
|
198
|
-
the canonical one that already exists; those survivors are counted and
|
|
199
|
-
reported rather than deleted, since the fixed view reads both
|
|
200
|
-
identically and deleting an edge is not a migration's business.
|
|
201
|
-
"""
|
|
202
|
-
report = {"edges": 0, "nodes": 0, "collisions": 0}
|
|
203
|
-
try:
|
|
204
|
-
before = int(
|
|
205
|
-
conn.execute(
|
|
206
|
-
"SELECT COUNT(*) FROM edges_v2 WHERE legacy_type = type"
|
|
207
|
-
).fetchone()[0]
|
|
208
|
-
or 0
|
|
209
|
-
)
|
|
210
|
-
conn.execute(
|
|
211
|
-
"UPDATE OR IGNORE edges_v2 SET legacy_type='' WHERE legacy_type = type"
|
|
212
|
-
)
|
|
213
|
-
after = int(
|
|
214
|
-
conn.execute(
|
|
215
|
-
"SELECT COUNT(*) FROM edges_v2 WHERE legacy_type = type"
|
|
216
|
-
).fetchone()[0]
|
|
217
|
-
or 0
|
|
218
|
-
)
|
|
219
|
-
report["edges"] = before - after
|
|
220
|
-
report["collisions"] = after
|
|
221
|
-
cursor = conn.execute(
|
|
222
|
-
"UPDATE nodes_v2 SET legacy_type=NULL WHERE legacy_type = ''"
|
|
223
|
-
)
|
|
224
|
-
report["nodes"] = int(cursor.rowcount or 0)
|
|
225
|
-
except sqlite3.Error as exc:
|
|
226
|
-
# The projection is derived; a migration that cannot run leaves the
|
|
227
|
-
# data exactly as it was and the fixed view still reads it right.
|
|
228
|
-
logging.debug("knowledge_graph: legacy_type normalization skipped: %s", exc)
|
|
229
|
-
return report
|
|
230
|
-
|
|
231
|
-
def _backup_before_v2_flip(self) -> Optional[str]:
|
|
232
|
-
"""Create one local SQLite backup before the v2 write-master flip."""
|
|
233
|
-
if not self.db_path.exists() or self.db_path.stat().st_size == 0:
|
|
234
|
-
return None
|
|
235
|
-
with self._connect() as conn:
|
|
236
|
-
try:
|
|
237
|
-
stamped = conn.execute(
|
|
238
|
-
"SELECT value FROM kg_meta WHERE key=?", (_V2_WRITE_MASTER_KEY,)
|
|
239
|
-
).fetchone()
|
|
240
|
-
except sqlite3.Error:
|
|
241
|
-
stamped = None
|
|
242
|
-
if stamped:
|
|
243
|
-
return None
|
|
244
|
-
try:
|
|
245
|
-
rows = int(
|
|
246
|
-
conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0] or 0
|
|
247
|
-
)
|
|
248
|
-
except sqlite3.Error:
|
|
249
|
-
rows = 0
|
|
250
|
-
if rows == 0:
|
|
251
|
-
return None
|
|
252
|
-
conn.execute("PRAGMA wal_checkpoint(FULL)")
|
|
253
|
-
backup_dir = self.db_path.parent / "backups"
|
|
254
|
-
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
255
|
-
stamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
|
256
|
-
dest = (
|
|
257
|
-
backup_dir / f"{self.db_path.stem}.pre-v2-write-master.{stamp}.sqlite"
|
|
258
|
-
)
|
|
259
|
-
conn.execute("VACUUM INTO ?", (str(dest),))
|
|
260
|
-
return str(dest)
|
|
261
|
-
|
|
262
|
-
def _projection_version(self, conn: sqlite3.Connection) -> int:
|
|
263
|
-
"""Return the stored v2 projection layout version (0 if unknown).
|
|
264
|
-
|
|
265
|
-
A fresh DB (kg_meta absent) raises ``sqlite3.OperationalError`` here and
|
|
266
|
-
is correctly treated as version 0 → rebuild. Only sqlite errors are
|
|
267
|
-
swallowed so a real bug doesn't masquerade as a stale projection.
|
|
268
|
-
"""
|
|
269
|
-
try:
|
|
270
|
-
row = conn.execute(
|
|
271
|
-
"SELECT value FROM kg_meta WHERE key='projection_version'"
|
|
272
|
-
).fetchone()
|
|
273
|
-
return int(row["value"]) if row and row["value"] is not None else 0
|
|
274
|
-
except sqlite3.Error:
|
|
275
|
-
return 0
|
|
276
|
-
|
|
277
|
-
def _backfill_v2_if_needed(self, *, force: bool = False) -> None:
|
|
278
|
-
"""Project legacy nodes/edges into v2 on a fresh transaction.
|
|
279
|
-
|
|
280
|
-
Thin wrapper around :meth:`_backfill_v2_on` for callers (tests, ad-hoc
|
|
281
|
-
re-sync) that aren't already inside the migration transaction.
|
|
282
|
-
"""
|
|
283
|
-
try:
|
|
284
|
-
with self._connect() as conn:
|
|
285
|
-
self._backfill_v2_on(conn, force=force)
|
|
286
|
-
except Exception as ex:
|
|
287
|
-
logging.warning("knowledge_graph: v2 backfill skipped: %s", ex)
|
|
288
|
-
|
|
289
|
-
def _backfill_v2_on(self, conn: sqlite3.Connection, *, force: bool = False) -> None:
|
|
290
|
-
"""Project legacy nodes/edges into the normalized v2 tables on ``conn``.
|
|
291
|
-
|
|
292
|
-
Non-destructive to legacy. ``force`` rebuilds unconditionally (used after
|
|
293
|
-
a layout migration); otherwise it only projects when v2 is empty. The v2
|
|
294
|
-
graph is a derived projection, so clearing + rebuilding it is always safe.
|
|
295
|
-
Idempotent: no-ops once v2 carries the current projection. Copies the
|
|
296
|
-
legacy column values **verbatim** so the kgv2_* views are byte-faithful.
|
|
297
|
-
"""
|
|
298
|
-
legacy_nodes = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
|
|
299
|
-
if legacy_nodes == 0:
|
|
300
|
-
return
|
|
301
|
-
v2_nodes = conn.execute("SELECT COUNT(*) FROM nodes_v2").fetchone()[0]
|
|
302
|
-
if v2_nodes > 0 and not force:
|
|
303
|
-
return # current projection already present
|
|
304
|
-
# (re)project: clear v2 graph (not authoritative) and rebuild
|
|
305
|
-
conn.execute("DELETE FROM edges_v2")
|
|
306
|
-
conn.execute("DELETE FROM nodes_v2")
|
|
307
|
-
n = e = 0
|
|
308
|
-
for r in conn.execute(
|
|
309
|
-
"SELECT id, type, title, summary, metadata_json, created_at, updated_at FROM nodes"
|
|
310
|
-
).fetchall():
|
|
311
|
-
self._v2_project_node(
|
|
312
|
-
conn,
|
|
313
|
-
r["id"],
|
|
314
|
-
r["type"],
|
|
315
|
-
r["title"],
|
|
316
|
-
r["summary"],
|
|
317
|
-
r["metadata_json"],
|
|
318
|
-
created_at=r["created_at"],
|
|
319
|
-
updated_at=r["updated_at"],
|
|
320
|
-
)
|
|
321
|
-
n += 1
|
|
322
|
-
for r in conn.execute(
|
|
323
|
-
"SELECT id, from_node, to_node, type, weight, metadata_json, created_at FROM edges"
|
|
324
|
-
).fetchall():
|
|
325
|
-
self._v2_project_edge(
|
|
326
|
-
conn,
|
|
327
|
-
r["from_node"],
|
|
328
|
-
r["to_node"],
|
|
329
|
-
r["type"],
|
|
330
|
-
float(r["weight"] or 1.0),
|
|
331
|
-
r["metadata_json"],
|
|
332
|
-
edge_id=r["id"],
|
|
333
|
-
created_at=r["created_at"],
|
|
334
|
-
)
|
|
335
|
-
e += 1
|
|
336
|
-
logging.info(
|
|
337
|
-
"knowledge_graph: projected legacy → v2 (%d nodes, %d edges)", n, e
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
def _v2_project_node(
|
|
341
|
-
self,
|
|
342
|
-
conn: sqlite3.Connection,
|
|
343
|
-
node_id: str,
|
|
344
|
-
node_type: str,
|
|
345
|
-
title: str,
|
|
346
|
-
summary: Optional[str],
|
|
347
|
-
metadata_json: Optional[str],
|
|
348
|
-
*,
|
|
349
|
-
created_at: Optional[str] = None,
|
|
350
|
-
updated_at: Optional[str] = None,
|
|
351
|
-
owner: Optional[str] = None,
|
|
352
|
-
workspace_id: Optional[str] = None,
|
|
353
|
-
visibility: Optional[str] = None,
|
|
354
|
-
strict: bool = False,
|
|
355
|
-
) -> None:
|
|
356
|
-
if KGStoreV2 is None:
|
|
357
|
-
if strict:
|
|
358
|
-
raise RuntimeError("Knowledge Graph v2 schema is unavailable")
|
|
359
|
-
return
|
|
360
|
-
ts = updated_at or _now()
|
|
361
|
-
norm_type = (
|
|
362
|
-
NodeType.from_legacy(node_type).value if NodeType is not None else node_type
|
|
363
|
-
)
|
|
364
|
-
# Scope resolution: explicit param > metadata hints > legacy-global.
|
|
365
|
-
# 'legacy' (not 'private') marks unscoped rows — the column default
|
|
366
|
-
# must never silently privatize previously machine-shared data.
|
|
367
|
-
meta = _safe_loads(metadata_json) if metadata_json else {}
|
|
368
|
-
owner = owner or meta.get("user_email") or meta.get("owner") or None
|
|
369
|
-
workspace_id = workspace_id or meta.get("workspace_id") or None
|
|
370
|
-
visibility = visibility or ("legacy" if workspace_id is None else "workspace")
|
|
371
|
-
try:
|
|
372
|
-
conn.execute(
|
|
373
|
-
"""
|
|
374
|
-
INSERT INTO nodes_v2(id, type, legacy_type, label, summary, attrs,
|
|
375
|
-
owner_id, workspace_id, visibility,
|
|
376
|
-
created_at, updated_at, importance_score)
|
|
377
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0.0)
|
|
378
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
379
|
-
type=excluded.type, legacy_type=excluded.legacy_type,
|
|
380
|
-
label=excluded.label, summary=excluded.summary,
|
|
381
|
-
attrs=excluded.attrs, updated_at=excluded.updated_at,
|
|
382
|
-
owner_id=COALESCE(excluded.owner_id, nodes_v2.owner_id),
|
|
383
|
-
workspace_id=COALESCE(excluded.workspace_id, nodes_v2.workspace_id),
|
|
384
|
-
visibility=CASE WHEN excluded.visibility != 'legacy'
|
|
385
|
-
THEN excluded.visibility
|
|
386
|
-
ELSE nodes_v2.visibility END
|
|
387
|
-
""",
|
|
388
|
-
(
|
|
389
|
-
node_id,
|
|
390
|
-
norm_type,
|
|
391
|
-
node_type,
|
|
392
|
-
title,
|
|
393
|
-
summary,
|
|
394
|
-
metadata_json if metadata_json is not None else "{}",
|
|
395
|
-
owner,
|
|
396
|
-
workspace_id,
|
|
397
|
-
visibility,
|
|
398
|
-
created_at or ts,
|
|
399
|
-
ts,
|
|
400
|
-
),
|
|
401
|
-
)
|
|
402
|
-
except Exception as ex:
|
|
403
|
-
if strict:
|
|
404
|
-
raise
|
|
405
|
-
logging.debug(
|
|
406
|
-
"knowledge_graph: v2 node projection skipped (%s): %s", node_id, ex
|
|
407
|
-
)
|
|
408
|
-
|
|
409
|
-
def _v2_project_edge(
|
|
410
|
-
self,
|
|
411
|
-
conn: sqlite3.Connection,
|
|
412
|
-
from_node: str,
|
|
413
|
-
to_node: str,
|
|
414
|
-
edge_type: str,
|
|
415
|
-
weight: float,
|
|
416
|
-
metadata_json: Optional[str],
|
|
417
|
-
*,
|
|
418
|
-
edge_id: Optional[str] = None,
|
|
419
|
-
created_at: Optional[str] = None,
|
|
420
|
-
strict: bool = False,
|
|
421
|
-
legacy_type: Optional[str] = None,
|
|
422
|
-
) -> None:
|
|
423
|
-
if KGStoreV2 is None:
|
|
424
|
-
if strict:
|
|
425
|
-
raise RuntimeError("Knowledge Graph v2 schema is unavailable")
|
|
426
|
-
return
|
|
427
|
-
explicit_legacy_type = legacy_type is not None
|
|
428
|
-
leg_type = legacy_type if explicit_legacy_type else edge_type
|
|
429
|
-
# Native canonical writes (and write-door dedupes) use legacy_type=''
|
|
430
|
-
# so (source,target,type) is the effective key.
|
|
431
|
-
# Import paths can pass distinct legacy_type to keep colliding legacy
|
|
432
|
-
# labels as separate rows (lossless for old data).
|
|
433
|
-
if leg_type and EdgeType is not None:
|
|
434
|
-
try:
|
|
435
|
-
if EdgeType.from_legacy(leg_type).value == leg_type:
|
|
436
|
-
leg_type = ""
|
|
437
|
-
except ValueError:
|
|
438
|
-
quiet()
|
|
439
|
-
norm_type = (
|
|
440
|
-
EdgeType.from_legacy(edge_type).value if EdgeType is not None else edge_type
|
|
441
|
-
)
|
|
442
|
-
if explicit_legacy_type and leg_type:
|
|
443
|
-
eid = f"edge:{_sha256_text(f'{from_node}|{norm_type}|{to_node}|{leg_type}')[:24]}"
|
|
444
|
-
else:
|
|
445
|
-
eid = edge_id or f"edge:{_sha256_text(f'{from_node}|{norm_type}|{to_node}')[:24]}"
|
|
446
|
-
meta_str = metadata_json if metadata_json is not None else "{}"
|
|
447
|
-
confidence = float(_safe_loads(meta_str).get("confidence", 1.0))
|
|
448
|
-
try:
|
|
449
|
-
conn.execute(
|
|
450
|
-
"""
|
|
451
|
-
INSERT INTO edges_v2(id, source, target, type, legacy_type, weight,
|
|
452
|
-
confidence, evidence, metadata, created_by, created_at)
|
|
453
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, '[]', ?, 'legacy', ?)
|
|
454
|
-
ON CONFLICT(source, target, type, legacy_type) DO UPDATE SET
|
|
455
|
-
weight=max(edges_v2.weight, excluded.weight),
|
|
456
|
-
confidence=excluded.confidence,
|
|
457
|
-
metadata=excluded.metadata
|
|
458
|
-
""",
|
|
459
|
-
(
|
|
460
|
-
eid,
|
|
461
|
-
from_node,
|
|
462
|
-
to_node,
|
|
463
|
-
norm_type,
|
|
464
|
-
leg_type,
|
|
465
|
-
float(weight),
|
|
466
|
-
confidence,
|
|
467
|
-
meta_str,
|
|
468
|
-
created_at or _now(),
|
|
469
|
-
),
|
|
470
|
-
)
|
|
471
|
-
# Temporal record: every observation of this relationship is kept
|
|
472
|
-
# (the UNIQUE upsert + weight=max alone would erase recurrence).
|
|
473
|
-
row = conn.execute(
|
|
474
|
-
"SELECT id FROM edges_v2 WHERE source=? AND target=? AND type=? AND legacy_type=?",
|
|
475
|
-
(from_node, to_node, norm_type, leg_type),
|
|
476
|
-
).fetchone()
|
|
477
|
-
if row is not None:
|
|
478
|
-
conn.execute(
|
|
479
|
-
"INSERT INTO edge_occurrences(edge_id, observed_at, weight, source) VALUES (?, ?, ?, ?)",
|
|
480
|
-
(
|
|
481
|
-
row["id"],
|
|
482
|
-
created_at or _now(),
|
|
483
|
-
float(weight),
|
|
484
|
-
_safe_loads(meta_str).get("source"),
|
|
485
|
-
),
|
|
486
|
-
)
|
|
487
|
-
except Exception as ex:
|
|
488
|
-
if strict:
|
|
489
|
-
raise
|
|
490
|
-
logging.debug(
|
|
491
|
-
"knowledge_graph: v2 edge projection skipped (%s->%s): %s",
|
|
492
|
-
from_node,
|
|
493
|
-
to_node,
|
|
494
|
-
ex,
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
def _v2_delete_nodes(self, conn: sqlite3.Connection, ids) -> None:
|
|
498
|
-
"""Mirror legacy node deletions into v2 (edges_v2 cascade on the FK)."""
|
|
499
|
-
if KGStoreV2 is None:
|
|
500
|
-
return
|
|
501
|
-
ids = list(ids)
|
|
502
|
-
if not ids:
|
|
503
|
-
return
|
|
504
|
-
ph = ",".join("?" * len(ids))
|
|
505
|
-
try:
|
|
506
|
-
conn.execute(f"DELETE FROM nodes_v2 WHERE id IN ({ph})", ids)
|
|
507
|
-
except Exception as ex:
|
|
508
|
-
logging.debug("knowledge_graph: v2 node delete mirror skipped: %s", ex)
|
|
509
|
-
|
|
510
|
-
def _v2_delete_edges_from(self, conn: sqlite3.Connection, node_id: str) -> None:
|
|
511
|
-
"""Mirror a legacy ``DELETE FROM edges WHERE from_node=?`` into v2."""
|
|
512
|
-
if KGStoreV2 is None:
|
|
513
|
-
return
|
|
514
|
-
try:
|
|
515
|
-
conn.execute("DELETE FROM edges_v2 WHERE source=?", (node_id,))
|
|
516
|
-
except Exception as ex:
|
|
517
|
-
logging.debug("knowledge_graph: v2 edge delete mirror skipped: %s", ex)
|
|
518
|
-
|