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,524 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
# ruff: noqa: F403,F405
|
|
6
|
-
from ._kg_common import * # noqa: F403,F401
|
|
7
|
-
|
|
8
|
-
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
9
|
-
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
10
|
-
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
13
|
-
else:
|
|
14
|
-
_Core = object
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class KnowledgeGraphProvenanceMixin(_Core):
|
|
19
|
-
def record_provenance(
|
|
20
|
-
self,
|
|
21
|
-
*,
|
|
22
|
-
node_id: str,
|
|
23
|
-
source_type: str,
|
|
24
|
-
pipeline: str = "unified-ingestion",
|
|
25
|
-
source_uri: Optional[str] = None,
|
|
26
|
-
content_hash: Optional[str] = None,
|
|
27
|
-
title: Optional[str] = None,
|
|
28
|
-
owner: Optional[str] = None,
|
|
29
|
-
workspace_id: Optional[str] = None,
|
|
30
|
-
captured_at: Optional[str] = None,
|
|
31
|
-
modified_at: Optional[str] = None,
|
|
32
|
-
embedded: bool = False,
|
|
33
|
-
linked: bool = False,
|
|
34
|
-
duplicate: bool = False,
|
|
35
|
-
agent_used: Optional[str] = None,
|
|
36
|
-
chunk_count: int = 0,
|
|
37
|
-
permissions: Optional[Dict[str, Any]] = None,
|
|
38
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
39
|
-
) -> Dict[str, Any]:
|
|
40
|
-
"""Record where an ingested node came from (upsert by origin).
|
|
41
|
-
|
|
42
|
-
Row identity is ``(node, content, source_type, source_uri, pipeline)``
|
|
43
|
-
— deliberately *not* the wall clock. Through 11.0.x the basis included
|
|
44
|
-
a second-resolution timestamp, which made the record's identity depend
|
|
45
|
-
on when it happened: re-ingesting unchanged content twice inside the
|
|
46
|
-
same second collapsed onto one row, and one second later appended a
|
|
47
|
-
duplicate. That is not an audit trail, it is a race — the same class of
|
|
48
|
-
defect as the 11.0.0 review-item ids — and it grew this table (and the
|
|
49
|
-
"recent ingestions" list built from it) without bound on every re-scan
|
|
50
|
-
of an unchanged folder or vault.
|
|
51
|
-
|
|
52
|
-
With the clock out of the basis, re-ingesting the same content from the
|
|
53
|
-
same origin *updates* one record (``created_at`` moves to the latest
|
|
54
|
-
sighting, so "recently seen" stays true), while genuinely new content or
|
|
55
|
-
a genuinely different origin — another source URI, another pipeline —
|
|
56
|
-
still appends its own record. The timestamp is data on the row, never
|
|
57
|
-
part of its identity. Every individual ingest event remains visible in
|
|
58
|
-
the audit log (``kg_ingest``), which is where per-event history belongs.
|
|
59
|
-
"""
|
|
60
|
-
now = _now()
|
|
61
|
-
prov_basis = "|".join([
|
|
62
|
-
node_id,
|
|
63
|
-
content_hash or "",
|
|
64
|
-
source_type,
|
|
65
|
-
source_uri or "",
|
|
66
|
-
pipeline,
|
|
67
|
-
])
|
|
68
|
-
prov_id = f"prov:{_sha256_text(prov_basis)[:24]}"
|
|
69
|
-
with self._connect() as conn:
|
|
70
|
-
conn.execute(
|
|
71
|
-
"""
|
|
72
|
-
INSERT OR REPLACE INTO ingestion_provenance(
|
|
73
|
-
id, node_id, source_type, source_uri, content_hash, title, pipeline,
|
|
74
|
-
owner, workspace_id, captured_at, modified_at, embedded, linked,
|
|
75
|
-
duplicate, agent_used, chunk_count, permissions_json, metadata_json, created_at)
|
|
76
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
77
|
-
""",
|
|
78
|
-
(
|
|
79
|
-
prov_id,
|
|
80
|
-
node_id,
|
|
81
|
-
source_type,
|
|
82
|
-
source_uri,
|
|
83
|
-
content_hash,
|
|
84
|
-
title,
|
|
85
|
-
pipeline,
|
|
86
|
-
owner,
|
|
87
|
-
workspace_id,
|
|
88
|
-
captured_at,
|
|
89
|
-
modified_at,
|
|
90
|
-
1 if embedded else 0,
|
|
91
|
-
1 if linked else 0,
|
|
92
|
-
1 if duplicate else 0,
|
|
93
|
-
agent_used,
|
|
94
|
-
int(chunk_count or 0),
|
|
95
|
-
_json(permissions or {}),
|
|
96
|
-
_json(metadata or {}),
|
|
97
|
-
now,
|
|
98
|
-
),
|
|
99
|
-
)
|
|
100
|
-
return {"id": prov_id, "node_id": node_id, "created_at": now}
|
|
101
|
-
|
|
102
|
-
@staticmethod
|
|
103
|
-
def _provenance_row(row: sqlite3.Row) -> Dict[str, Any]:
|
|
104
|
-
return {
|
|
105
|
-
"id": row["id"],
|
|
106
|
-
"node_id": row["node_id"],
|
|
107
|
-
"source_type": row["source_type"],
|
|
108
|
-
"source_uri": row["source_uri"],
|
|
109
|
-
"content_hash": row["content_hash"],
|
|
110
|
-
"title": row["title"],
|
|
111
|
-
"pipeline": row["pipeline"],
|
|
112
|
-
"owner": row["owner"],
|
|
113
|
-
"workspace_id": row["workspace_id"],
|
|
114
|
-
"captured_at": row["captured_at"],
|
|
115
|
-
"modified_at": row["modified_at"],
|
|
116
|
-
"embedded": bool(row["embedded"]),
|
|
117
|
-
"linked": bool(row["linked"]),
|
|
118
|
-
"duplicate": bool(row["duplicate"]),
|
|
119
|
-
"agent_used": row["agent_used"],
|
|
120
|
-
"chunk_count": row["chunk_count"],
|
|
121
|
-
"permissions": _safe_loads(row["permissions_json"]),
|
|
122
|
-
"metadata": _safe_loads(row["metadata_json"]),
|
|
123
|
-
"created_at": row["created_at"],
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
def get_provenance(self, node_id: str) -> Optional[Dict[str, Any]]:
|
|
127
|
-
"""Return the most recent provenance record for a node, or None."""
|
|
128
|
-
with self._connect() as conn:
|
|
129
|
-
row = conn.execute(
|
|
130
|
-
"SELECT * FROM ingestion_provenance WHERE node_id = ? "
|
|
131
|
-
"ORDER BY created_at DESC, rowid DESC LIMIT 1",
|
|
132
|
-
(node_id,),
|
|
133
|
-
).fetchone()
|
|
134
|
-
return self._provenance_row(row) if row else None
|
|
135
|
-
|
|
136
|
-
def list_provenance(
|
|
137
|
-
self, *, limit: int = 100, source_type: Optional[str] = None
|
|
138
|
-
) -> Dict[str, Any]:
|
|
139
|
-
"""Recent provenance records (newest first), optionally by source_type."""
|
|
140
|
-
limit = max(1, min(int(limit or 100), 1000))
|
|
141
|
-
with self._connect() as conn:
|
|
142
|
-
if source_type:
|
|
143
|
-
rows = conn.execute(
|
|
144
|
-
"SELECT * FROM ingestion_provenance WHERE source_type = ? "
|
|
145
|
-
"ORDER BY created_at DESC, rowid DESC LIMIT ?",
|
|
146
|
-
(source_type, limit),
|
|
147
|
-
).fetchall()
|
|
148
|
-
else:
|
|
149
|
-
rows = conn.execute(
|
|
150
|
-
"SELECT * FROM ingestion_provenance "
|
|
151
|
-
"ORDER BY created_at DESC, rowid DESC LIMIT ?",
|
|
152
|
-
(limit,),
|
|
153
|
-
).fetchall()
|
|
154
|
-
return {
|
|
155
|
-
"items": [self._provenance_row(r) for r in rows],
|
|
156
|
-
"count": len(rows),
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
def provenance_coverage(self) -> Dict[str, Any]:
|
|
160
|
-
"""How much of the brain is explainable: nodes with vs without
|
|
161
|
-
provenance, per node type — the honesty metric for 'every source goes
|
|
162
|
-
through the pipeline'. Pre-v4 nodes ingested before provenance existed
|
|
163
|
-
legitimately count as uncovered."""
|
|
164
|
-
nt, _ = self._read_tables()
|
|
165
|
-
with self._connect() as conn:
|
|
166
|
-
total = conn.execute(f"SELECT COUNT(*) FROM {nt}").fetchone()[0]
|
|
167
|
-
covered = conn.execute(
|
|
168
|
-
f"SELECT COUNT(*) FROM {nt} WHERE id IN (SELECT DISTINCT node_id FROM ingestion_provenance)"
|
|
169
|
-
).fetchone()[0]
|
|
170
|
-
uncovered_by_type = {
|
|
171
|
-
row["type"]: row["c"]
|
|
172
|
-
for row in conn.execute(
|
|
173
|
-
f"""
|
|
174
|
-
SELECT type, COUNT(*) AS c FROM {nt}
|
|
175
|
-
WHERE id NOT IN (SELECT DISTINCT node_id FROM ingestion_provenance)
|
|
176
|
-
GROUP BY type ORDER BY c DESC LIMIT 20
|
|
177
|
-
"""
|
|
178
|
-
).fetchall()
|
|
179
|
-
}
|
|
180
|
-
by_source = {
|
|
181
|
-
row["source_type"]: row["c"]
|
|
182
|
-
for row in conn.execute(
|
|
183
|
-
"SELECT source_type, COUNT(*) AS c FROM ingestion_provenance GROUP BY source_type"
|
|
184
|
-
).fetchall()
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
"total_nodes": total,
|
|
188
|
-
"nodes_with_provenance": covered,
|
|
189
|
-
"coverage_ratio": round(covered / total, 4) if total else None,
|
|
190
|
-
"uncovered_by_type": uncovered_by_type,
|
|
191
|
-
"provenance_by_source_type": by_source,
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
def provenance_stats(self) -> Dict[str, Any]:
|
|
195
|
-
"""Aggregate provenance counts for the Knowledge Graph status surface."""
|
|
196
|
-
with self._connect() as conn:
|
|
197
|
-
total = conn.execute(
|
|
198
|
-
"SELECT COUNT(*) AS c FROM ingestion_provenance"
|
|
199
|
-
).fetchone()["c"]
|
|
200
|
-
by_source = {
|
|
201
|
-
r["source_type"]: r["c"]
|
|
202
|
-
for r in conn.execute(
|
|
203
|
-
"SELECT source_type, COUNT(*) AS c FROM ingestion_provenance GROUP BY source_type"
|
|
204
|
-
).fetchall()
|
|
205
|
-
}
|
|
206
|
-
embedded = conn.execute(
|
|
207
|
-
"SELECT COUNT(*) AS c FROM ingestion_provenance WHERE embedded = 1"
|
|
208
|
-
).fetchone()["c"]
|
|
209
|
-
duplicates = conn.execute(
|
|
210
|
-
"SELECT COUNT(*) AS c FROM ingestion_provenance WHERE duplicate = 1"
|
|
211
|
-
).fetchone()["c"]
|
|
212
|
-
last = conn.execute(
|
|
213
|
-
"SELECT created_at FROM ingestion_provenance ORDER BY created_at DESC LIMIT 1"
|
|
214
|
-
).fetchone()
|
|
215
|
-
return {
|
|
216
|
-
"total": total,
|
|
217
|
-
"by_source_type": by_source,
|
|
218
|
-
"embedded": embedded,
|
|
219
|
-
"duplicates": duplicates,
|
|
220
|
-
"last_ingested_at": last["created_at"] if last else None,
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
def schema_versions(self) -> Dict[str, Any]:
|
|
224
|
-
"""Versions an exporter stamps and an importer validates against."""
|
|
225
|
-
try:
|
|
226
|
-
from .schema import EMBED_DIM as _EMBED_DIM
|
|
227
|
-
from .schema import KG_SCHEMA_V2_VERSION as _V2
|
|
228
|
-
except Exception: # pragma: no cover - kg_schema always importable in practice
|
|
229
|
-
_EMBED_DIM, _V2 = 1024, 2
|
|
230
|
-
return {
|
|
231
|
-
"graph_schema_version": GRAPH_SCHEMA_VERSION,
|
|
232
|
-
"db_format_version": _KG_DB_FORMAT_VERSION,
|
|
233
|
-
"kg_v2_schema_version": _V2,
|
|
234
|
-
"projection_version": _PROJECTION_VERSION,
|
|
235
|
-
"embed_dim": _EMBED_DIM,
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
def export_graph_data(
|
|
239
|
-
self,
|
|
240
|
-
*,
|
|
241
|
-
workspace_id: Optional[str] = None,
|
|
242
|
-
include_legacy_global: bool = False,
|
|
243
|
-
) -> Dict[str, Any]:
|
|
244
|
-
"""Raw, lossless logical export of the graph (nodes/edges/chunks/sources/
|
|
245
|
-
provenance). Vector embeddings are intentionally omitted — the importer
|
|
246
|
-
re-derives them with *its own* embedder (:meth:`import_graph_data`
|
|
247
|
-
embeds on write and then reindexes, reporting the result under
|
|
248
|
-
``index``) — so the artifact stays portable and small. Use
|
|
249
|
-
:meth:`backup_database` for a faithful binary copy incl. embeddings.
|
|
250
|
-
|
|
251
|
-
``workspace_id`` REALLY filters (v4): the artifact contains only nodes
|
|
252
|
-
scoped to that workspace, with edges/chunks/provenance restricted to the
|
|
253
|
-
surviving nodes. Legacy-global rows require the explicit
|
|
254
|
-
``include_legacy_global=True`` migration/compatibility opt-in. Pre-v4
|
|
255
|
-
this parameter was stamped into the header while the data exported
|
|
256
|
-
everything — a header that lied.
|
|
257
|
-
"""
|
|
258
|
-
with self._connect() as conn:
|
|
259
|
-
|
|
260
|
-
def rows(table: str):
|
|
261
|
-
return [
|
|
262
|
-
dict(r) for r in conn.execute(f"SELECT * FROM {table}").fetchall()
|
|
263
|
-
]
|
|
264
|
-
|
|
265
|
-
if workspace_id:
|
|
266
|
-
scope_sql = "workspace_id = ?"
|
|
267
|
-
if include_legacy_global:
|
|
268
|
-
scope_sql += " OR workspace_id IS NULL"
|
|
269
|
-
keep_ids = {
|
|
270
|
-
row["id"]
|
|
271
|
-
for row in conn.execute(
|
|
272
|
-
f"SELECT id FROM nodes_v2 WHERE {scope_sql}",
|
|
273
|
-
(workspace_id,),
|
|
274
|
-
).fetchall()
|
|
275
|
-
}
|
|
276
|
-
nodes = [n for n in rows("nodes") if n["id"] in keep_ids]
|
|
277
|
-
edges = [
|
|
278
|
-
e
|
|
279
|
-
for e in rows("edges")
|
|
280
|
-
if e["from_node"] in keep_ids and e["to_node"] in keep_ids
|
|
281
|
-
]
|
|
282
|
-
chunks = [c for c in rows("chunks") if c["source_node"] in keep_ids]
|
|
283
|
-
provenance = [
|
|
284
|
-
p for p in rows("ingestion_provenance") if p["node_id"] in keep_ids
|
|
285
|
-
]
|
|
286
|
-
data = {
|
|
287
|
-
"nodes": nodes,
|
|
288
|
-
"edges": edges,
|
|
289
|
-
"chunks": chunks,
|
|
290
|
-
"knowledge_sources": rows("knowledge_sources"),
|
|
291
|
-
"provenance": provenance,
|
|
292
|
-
}
|
|
293
|
-
else:
|
|
294
|
-
data = {
|
|
295
|
-
"nodes": rows("nodes"),
|
|
296
|
-
"edges": rows("edges"),
|
|
297
|
-
"chunks": rows("chunks"),
|
|
298
|
-
"knowledge_sources": rows("knowledge_sources"),
|
|
299
|
-
"provenance": rows("ingestion_provenance"),
|
|
300
|
-
}
|
|
301
|
-
data["counts"] = {k: len(v) for k, v in data.items()}
|
|
302
|
-
return data
|
|
303
|
-
|
|
304
|
-
def _reindex_after_import(self) -> Dict[str, Any]:
|
|
305
|
-
"""Bring the vector index in line with what was just imported.
|
|
306
|
-
|
|
307
|
-
A logical artifact carries no embeddings (see :meth:`export_graph_data`)
|
|
308
|
-
so they have to be re-derived on this machine. The write door
|
|
309
|
-
(``_upsert_node`` / ``_upsert_chunk``) already embeds inline, which
|
|
310
|
-
makes this pass a *verification* in the common case — but "the index is
|
|
311
|
-
consistent after an import" has to be a guarantee the import makes, not
|
|
312
|
-
an accident of where the embedding call happens to live. It also
|
|
313
|
-
records the embedder fingerprint, which the inline write path never
|
|
314
|
-
does; without it ``index_status()`` reports ``recorded: None`` and the
|
|
315
|
-
``stale_embedder`` honesty signal is dead after every import.
|
|
316
|
-
|
|
317
|
-
Never raises. The graph rows are already committed at this point, so an
|
|
318
|
-
embedding provider that dies here means "content imported, recall
|
|
319
|
-
degraded" — reported with the repo's existing ``vector_freshness``
|
|
320
|
-
vocabulary (``ready`` / ``pending`` / ``stale_embedder`` /
|
|
321
|
-
``unavailable``) plus an explicit ``degraded`` flag — never a rollback
|
|
322
|
-
of work that already landed, and never a silent success.
|
|
323
|
-
"""
|
|
324
|
-
reindexed = 0
|
|
325
|
-
try:
|
|
326
|
-
outcome = self.rebuild_vector_index(full=False) or {}
|
|
327
|
-
reindexed = int(outcome.get("items_indexed") or 0)
|
|
328
|
-
except Exception as exc: # noqa: BLE001 — the import already committed
|
|
329
|
-
return {
|
|
330
|
-
"status": "unavailable",
|
|
331
|
-
"degraded": True,
|
|
332
|
-
"detail": (
|
|
333
|
-
"imported content is stored, but the vector index could not "
|
|
334
|
-
f"be rebuilt ({exc}); search stays lexical-only for the new "
|
|
335
|
-
"items until a rebuild succeeds"
|
|
336
|
-
),
|
|
337
|
-
"reindexed_items": 0,
|
|
338
|
-
}
|
|
339
|
-
report = self.vector_freshness() or {}
|
|
340
|
-
status = str(report.get("status") or "unavailable")
|
|
341
|
-
return {
|
|
342
|
-
"status": status,
|
|
343
|
-
"degraded": status != "ready",
|
|
344
|
-
"detail": report.get("detail"),
|
|
345
|
-
"pending_items": int(report.get("pending_items") or 0),
|
|
346
|
-
"total_items": int(report.get("total_items") or 0),
|
|
347
|
-
"reindexed_items": reindexed,
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
def import_graph_data(
|
|
351
|
-
self, data: Dict[str, Any], *, mode: str = "merge", dry_run: bool = False
|
|
352
|
-
) -> Dict[str, Any]:
|
|
353
|
-
"""Import a logical export back into the store.
|
|
354
|
-
|
|
355
|
-
``mode='merge'`` upserts on top of existing data (id collisions update);
|
|
356
|
-
``mode='replace'`` clears the graph first (including the derived vector
|
|
357
|
-
rows — the re-import re-embeds them inside the same transaction, so a
|
|
358
|
-
failed artifact rolls back to the previous index rather than an empty
|
|
359
|
-
one). ``dry_run=True`` reports the plan without writing. Refuses
|
|
360
|
-
artifacts from a NEWER graph schema than this build.
|
|
361
|
-
|
|
362
|
-
A completed import carries an ``index`` block reporting the state of
|
|
363
|
-
the re-derived vector index (see :meth:`_reindex_after_import`); when
|
|
364
|
-
``index["degraded"]`` is true, retrieval for the imported scope is
|
|
365
|
-
lexical-only until the index is rebuilt.
|
|
366
|
-
"""
|
|
367
|
-
nodes = data.get("nodes") or []
|
|
368
|
-
edges = data.get("edges") or []
|
|
369
|
-
chunks = data.get("chunks") or []
|
|
370
|
-
sources = data.get("knowledge_sources") or []
|
|
371
|
-
provenance = data.get("provenance") or []
|
|
372
|
-
|
|
373
|
-
header = data.get("header") or {}
|
|
374
|
-
incoming_schema = header.get("graph_schema_version")
|
|
375
|
-
if isinstance(incoming_schema, int) and incoming_schema > GRAPH_SCHEMA_VERSION:
|
|
376
|
-
raise ValueError(
|
|
377
|
-
f"Artifact graph_schema_version {incoming_schema} is newer than this "
|
|
378
|
-
f"build ({GRAPH_SCHEMA_VERSION}); refusing to import."
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
plan = {
|
|
382
|
-
"mode": mode,
|
|
383
|
-
"nodes": len(nodes),
|
|
384
|
-
"edges": len(edges),
|
|
385
|
-
"chunks": len(chunks),
|
|
386
|
-
"knowledge_sources": len(sources),
|
|
387
|
-
"provenance": len(provenance),
|
|
388
|
-
}
|
|
389
|
-
if dry_run:
|
|
390
|
-
plan["dry_run"] = True
|
|
391
|
-
return plan
|
|
392
|
-
|
|
393
|
-
with self._connect() as conn:
|
|
394
|
-
if mode == "replace":
|
|
395
|
-
# Keep replacement imports transactional. The old clear_all()
|
|
396
|
-
# path committed before the import started, so a malformed
|
|
397
|
-
# artifact could leave a cleared or partially rebuilt graph.
|
|
398
|
-
# These deletes roll back with the rest of the import.
|
|
399
|
-
for table in (
|
|
400
|
-
"local_file_index",
|
|
401
|
-
"knowledge_sources",
|
|
402
|
-
"chunks",
|
|
403
|
-
"edges",
|
|
404
|
-
"nodes",
|
|
405
|
-
"vector_embeddings",
|
|
406
|
-
):
|
|
407
|
-
conn.execute(f"DELETE FROM {table}")
|
|
408
|
-
if KGStoreV2 is not None:
|
|
409
|
-
conn.execute("DELETE FROM edges_v2")
|
|
410
|
-
conn.execute("DELETE FROM nodes_v2")
|
|
411
|
-
for n in nodes:
|
|
412
|
-
self._upsert_node(
|
|
413
|
-
conn,
|
|
414
|
-
n["id"],
|
|
415
|
-
n["type"],
|
|
416
|
-
n.get("title") or "",
|
|
417
|
-
summary=n.get("summary") or "",
|
|
418
|
-
metadata=_safe_loads(n.get("metadata_json")),
|
|
419
|
-
raw=_safe_loads(n.get("raw_json")),
|
|
420
|
-
)
|
|
421
|
-
for c in chunks:
|
|
422
|
-
self._upsert_chunk(
|
|
423
|
-
conn,
|
|
424
|
-
chunk_id=c["id"],
|
|
425
|
-
source_node=c["source_node"],
|
|
426
|
-
text=c.get("text") or "",
|
|
427
|
-
metadata=_safe_loads(c.get("metadata_json")),
|
|
428
|
-
)
|
|
429
|
-
for e in edges:
|
|
430
|
-
e_meta = _safe_loads(e.get("metadata_json")) or {}
|
|
431
|
-
leg_label = e_meta.get("legacy_label")
|
|
432
|
-
if not leg_label:
|
|
433
|
-
orig = e.get("type") or ""
|
|
434
|
-
if orig:
|
|
435
|
-
# preserve whatever label came from export (legacy or canon)
|
|
436
|
-
leg_label = orig
|
|
437
|
-
self._upsert_edge(
|
|
438
|
-
conn,
|
|
439
|
-
e["from_node"],
|
|
440
|
-
e["to_node"],
|
|
441
|
-
e["type"],
|
|
442
|
-
weight=float(e.get("weight") or 1.0),
|
|
443
|
-
metadata=e_meta,
|
|
444
|
-
legacy_label=leg_label,
|
|
445
|
-
)
|
|
446
|
-
for s in sources:
|
|
447
|
-
conn.execute(
|
|
448
|
-
"""
|
|
449
|
-
INSERT OR REPLACE INTO knowledge_sources(
|
|
450
|
-
id, root_path, os_type, drive_id, label, status, include_ocr,
|
|
451
|
-
watch_enabled, consent_json, created_at, updated_at, last_scanned_at)
|
|
452
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
453
|
-
""",
|
|
454
|
-
(
|
|
455
|
-
s["id"],
|
|
456
|
-
s["root_path"],
|
|
457
|
-
s["os_type"],
|
|
458
|
-
s.get("drive_id"),
|
|
459
|
-
s.get("label"),
|
|
460
|
-
s.get("status") or "active",
|
|
461
|
-
int(s.get("include_ocr") or 0),
|
|
462
|
-
int(s.get("watch_enabled") or 0),
|
|
463
|
-
s.get("consent_json") or "{}",
|
|
464
|
-
s.get("created_at") or _now(),
|
|
465
|
-
s.get("updated_at") or _now(),
|
|
466
|
-
s.get("last_scanned_at"),
|
|
467
|
-
),
|
|
468
|
-
)
|
|
469
|
-
for p in provenance:
|
|
470
|
-
conn.execute(
|
|
471
|
-
"""
|
|
472
|
-
INSERT OR REPLACE INTO ingestion_provenance(
|
|
473
|
-
id, node_id, source_type, source_uri, content_hash, title, pipeline,
|
|
474
|
-
owner, workspace_id, captured_at, modified_at, embedded, linked,
|
|
475
|
-
duplicate, agent_used, chunk_count, permissions_json, metadata_json, created_at)
|
|
476
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
477
|
-
""",
|
|
478
|
-
(
|
|
479
|
-
p["id"],
|
|
480
|
-
p["node_id"],
|
|
481
|
-
p["source_type"],
|
|
482
|
-
p.get("source_uri"),
|
|
483
|
-
p.get("content_hash"),
|
|
484
|
-
p.get("title"),
|
|
485
|
-
p.get("pipeline") or "import",
|
|
486
|
-
p.get("owner"),
|
|
487
|
-
p.get("workspace_id"),
|
|
488
|
-
p.get("captured_at"),
|
|
489
|
-
p.get("modified_at"),
|
|
490
|
-
int(p.get("embedded") or 0),
|
|
491
|
-
int(p.get("linked") or 0),
|
|
492
|
-
int(p.get("duplicate") or 0),
|
|
493
|
-
p.get("agent_used"),
|
|
494
|
-
int(p.get("chunk_count") or 0),
|
|
495
|
-
p.get("permissions_json") or "{}",
|
|
496
|
-
p.get("metadata_json") or "{}",
|
|
497
|
-
p.get("created_at") or _now(),
|
|
498
|
-
),
|
|
499
|
-
)
|
|
500
|
-
plan["imported"] = True
|
|
501
|
-
plan["index"] = self._reindex_after_import()
|
|
502
|
-
return plan
|
|
503
|
-
|
|
504
|
-
def backup_database(self, dest_path) -> Path:
|
|
505
|
-
"""Write a clean, standalone snapshot of the live DB to ``dest_path``.
|
|
506
|
-
|
|
507
|
-
Uses ``VACUUM INTO`` (after a full WAL checkpoint) so the snapshot is a
|
|
508
|
-
defragmented, rollback-journal-mode database with no companion -wal/-shm
|
|
509
|
-
— which restores cleanly by a plain file copy. Captures all data incl.
|
|
510
|
-
the vector_embeddings BLOBs.
|
|
511
|
-
"""
|
|
512
|
-
dest = Path(dest_path)
|
|
513
|
-
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
514
|
-
if dest.exists():
|
|
515
|
-
dest.unlink() # VACUUM INTO requires the target to not exist
|
|
516
|
-
# Raw connection, not ``_connect()``: VACUUM cannot run inside a
|
|
517
|
-
# transaction, and ``_connect()`` wraps its block in one.
|
|
518
|
-
conn = self.storage_engine.connect()
|
|
519
|
-
try:
|
|
520
|
-
conn.execute("PRAGMA wal_checkpoint(FULL)")
|
|
521
|
-
conn.execute("VACUUM INTO ?", (str(dest),))
|
|
522
|
-
finally:
|
|
523
|
-
conn.close()
|
|
524
|
-
return dest
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
"""Optional cross-encoder rerank for hybrid retrieval (v9.9.5).
|
|
2
|
-
|
|
3
|
-
Default path is identity (fused score preserved) — no model download, no
|
|
4
|
-
latency tax, no unearned claims. Opt in with::
|
|
5
|
-
|
|
6
|
-
LATTICEAI_CROSS_ENCODER_RERANK=1
|
|
7
|
-
# optional model id (sentence-transformers CrossEncoder):
|
|
8
|
-
LATTICEAI_CROSS_ENCODER_MODEL=cross-encoder/ms-marco-MiniLM-L-6-v2
|
|
9
|
-
|
|
10
|
-
When the env kill-switch is off, or ``sentence_transformers`` / the model is
|
|
11
|
-
unavailable, :func:`rerank_matches` returns the candidates unchanged and
|
|
12
|
-
reports ``mode="identity"``. Failures never raise into the search path.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import logging
|
|
18
|
-
import os
|
|
19
|
-
from typing import Any, Dict, List, Optional
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger(__name__)
|
|
22
|
-
|
|
23
|
-
CROSS_ENCODER_RERANK_ENV = "LATTICEAI_CROSS_ENCODER_RERANK"
|
|
24
|
-
CROSS_ENCODER_MODEL_ENV = "LATTICEAI_CROSS_ENCODER_MODEL"
|
|
25
|
-
DEFAULT_CROSS_ENCODER_MODEL = "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
|
26
|
-
|
|
27
|
-
_model_cache: Dict[str, Any] = {}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _rerank_enabled() -> bool:
|
|
31
|
-
raw = os.getenv(CROSS_ENCODER_RERANK_ENV, "").strip().lower()
|
|
32
|
-
return raw in {"1", "true", "yes", "on"}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _model_id() -> str:
|
|
36
|
-
raw = os.getenv(CROSS_ENCODER_MODEL_ENV, "").strip()
|
|
37
|
-
return raw or DEFAULT_CROSS_ENCODER_MODEL
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _candidate_text(match: Dict[str, Any]) -> str:
|
|
41
|
-
parts = [
|
|
42
|
-
str(match.get("title") or ""),
|
|
43
|
-
str(match.get("summary") or ""),
|
|
44
|
-
str((match.get("metadata") or {}).get("snippet") or ""),
|
|
45
|
-
]
|
|
46
|
-
return " ".join(p for p in parts if p).strip() or str(match.get("node_id") or "")
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _load_cross_encoder(model_id: str) -> Any:
|
|
50
|
-
if model_id in _model_cache:
|
|
51
|
-
return _model_cache[model_id]
|
|
52
|
-
from sentence_transformers import CrossEncoder # type: ignore
|
|
53
|
-
|
|
54
|
-
model = CrossEncoder(model_id)
|
|
55
|
-
_model_cache[model_id] = model
|
|
56
|
-
return model
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def identity_rerank(
|
|
60
|
-
query: str,
|
|
61
|
-
candidates: List[Dict[str, Any]],
|
|
62
|
-
*,
|
|
63
|
-
top_k: Optional[int] = None,
|
|
64
|
-
) -> Dict[str, Any]:
|
|
65
|
-
"""Preserve fused ordering; stamp identity scores for an honest contract."""
|
|
66
|
-
del query # identity path does not use the query text
|
|
67
|
-
ranked = list(candidates)
|
|
68
|
-
for item in ranked:
|
|
69
|
-
item["rerank_score"] = float(item.get("score") or item.get("fused_score") or 0.0)
|
|
70
|
-
if top_k is not None:
|
|
71
|
-
ranked = ranked[: max(1, int(top_k))]
|
|
72
|
-
return {
|
|
73
|
-
"matches": ranked,
|
|
74
|
-
"mode": "identity",
|
|
75
|
-
"model": None,
|
|
76
|
-
"detail": None,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def cross_encoder_rerank(
|
|
81
|
-
query: str,
|
|
82
|
-
candidates: List[Dict[str, Any]],
|
|
83
|
-
*,
|
|
84
|
-
top_k: Optional[int] = None,
|
|
85
|
-
model_id: Optional[str] = None,
|
|
86
|
-
) -> Dict[str, Any]:
|
|
87
|
-
"""Score (query, candidate) pairs with a CrossEncoder when available."""
|
|
88
|
-
if not candidates:
|
|
89
|
-
return {
|
|
90
|
-
"matches": [],
|
|
91
|
-
"mode": "cross_encoder",
|
|
92
|
-
"model": model_id or _model_id(),
|
|
93
|
-
"detail": None,
|
|
94
|
-
}
|
|
95
|
-
mid = model_id or _model_id()
|
|
96
|
-
try:
|
|
97
|
-
model = _load_cross_encoder(mid)
|
|
98
|
-
except Exception as exc: # noqa: BLE001 — never break search
|
|
99
|
-
logger.info("cross-encoder unavailable (%s); falling back to identity", exc)
|
|
100
|
-
result = identity_rerank(query, candidates, top_k=top_k)
|
|
101
|
-
result["detail"] = f"cross_encoder_unavailable: {exc}"
|
|
102
|
-
return result
|
|
103
|
-
|
|
104
|
-
pairs = [[str(query or ""), _candidate_text(c)] for c in candidates]
|
|
105
|
-
try:
|
|
106
|
-
scores = model.predict(pairs)
|
|
107
|
-
except Exception as exc: # noqa: BLE001
|
|
108
|
-
logger.warning("cross-encoder predict failed: %s", exc)
|
|
109
|
-
result = identity_rerank(query, candidates, top_k=top_k)
|
|
110
|
-
result["detail"] = f"cross_encoder_predict_failed: {exc}"
|
|
111
|
-
return result
|
|
112
|
-
|
|
113
|
-
ranked = list(candidates)
|
|
114
|
-
# strict=False on purpose: a cross-encoder that returns fewer scores than
|
|
115
|
-
# candidates leaves the tail un-reranked at its fused score, which is a
|
|
116
|
-
# better outcome than dropping the whole rerank.
|
|
117
|
-
for item, score in zip(ranked, scores, strict=False):
|
|
118
|
-
item["rerank_score"] = float(score)
|
|
119
|
-
# Surface the rerank score as the primary ranking key while keeping
|
|
120
|
-
# the pre-rerank fused score under scores.fused for audit.
|
|
121
|
-
scores_map = item.setdefault("scores", {})
|
|
122
|
-
if isinstance(scores_map, dict):
|
|
123
|
-
scores_map.setdefault("fused", float(item.get("score") or 0.0))
|
|
124
|
-
scores_map["rerank"] = float(score)
|
|
125
|
-
item["score"] = float(score)
|
|
126
|
-
ranked.sort(key=lambda m: (-float(m.get("rerank_score") or 0.0), str(m.get("node_id") or "")))
|
|
127
|
-
if top_k is not None:
|
|
128
|
-
ranked = ranked[: max(1, int(top_k))]
|
|
129
|
-
for rank, match in enumerate(ranked, start=1):
|
|
130
|
-
match["rank"] = rank
|
|
131
|
-
return {
|
|
132
|
-
"matches": ranked,
|
|
133
|
-
"mode": "cross_encoder",
|
|
134
|
-
"model": mid,
|
|
135
|
-
"detail": None,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def rerank_matches(
|
|
140
|
-
query: str,
|
|
141
|
-
candidates: List[Dict[str, Any]],
|
|
142
|
-
*,
|
|
143
|
-
top_k: Optional[int] = None,
|
|
144
|
-
force: Optional[bool] = None,
|
|
145
|
-
) -> Dict[str, Any]:
|
|
146
|
-
"""Public entry: cross-encoder when enabled, else identity.
|
|
147
|
-
|
|
148
|
-
``force=True/False`` overrides the env kill-switch (tests only).
|
|
149
|
-
"""
|
|
150
|
-
enabled = _rerank_enabled() if force is None else bool(force)
|
|
151
|
-
if not enabled:
|
|
152
|
-
return identity_rerank(query, candidates, top_k=top_k)
|
|
153
|
-
return cross_encoder_rerank(query, candidates, top_k=top_k)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
__all__ = [
|
|
157
|
-
"CROSS_ENCODER_MODEL_ENV",
|
|
158
|
-
"CROSS_ENCODER_RERANK_ENV",
|
|
159
|
-
"DEFAULT_CROSS_ENCODER_MODEL",
|
|
160
|
-
"cross_encoder_rerank",
|
|
161
|
-
"identity_rerank",
|
|
162
|
-
"rerank_matches",
|
|
163
|
-
]
|