ltcai 11.5.2 → 11.6.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 +91 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +75 -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 +21 -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-CToZnOHz.js} +1 -1
- package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
- package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
- package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
- package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
- package/static/app/assets/circle-check-CY20XMCh.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
- package/static/app/assets/index-BovSWRmQ.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
- package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
- package/static/app/assets/search-DTxtjQ07.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.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/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/search-BcHqkjoy.js +0 -1
|
@@ -1,792 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Lattice AI — Knowledge Graph v2 schema (PPT spec aligned)
|
|
3
|
-
=========================================================
|
|
4
|
-
|
|
5
|
-
명세: ``lattice_ai_full_spec.pptx`` 슬라이드 20~22 (Node / Edge / Data Model)
|
|
6
|
-
|
|
7
|
-
목적
|
|
8
|
-
----
|
|
9
|
-
기존 ``knowledge_graph.py`` 의 자유 문자열 노드/엣지 타입을 **명시 enum + SQLite v2
|
|
10
|
-
스키마** 로 정식화한다. 이 모듈은 **스키마/초기화/프로젝션 지원** 역할만 담당한다:
|
|
11
|
-
``NodeType``/``EdgeType`` taxonomy + legacy 정규화 매핑, ``nodes_v2``/``edges_v2``
|
|
12
|
-
DDL(``SCHEMA_SQL``), 그리고 ``KGStoreV2``(스키마 init·heal·stats).
|
|
13
|
-
|
|
14
|
-
실제 데이터 read/write 는 ``knowledge_graph.py`` 의 ``KnowledgeGraphStore`` 가
|
|
15
|
-
legacy 테이블에 대한 dual-write 프로젝션(raw SQL) + ``kgv2_*`` 재구성 뷰로 수행한다.
|
|
16
|
-
(과거의 native ``Node``/``Edge`` 모델과 ``KGStoreV2.upsert_*``/``get_node``/
|
|
17
|
-
``search_*`` API 는 production 에서 쓰이지 않아 제거되었다.)
|
|
18
|
-
|
|
19
|
-
설계 원칙
|
|
20
|
-
---------
|
|
21
|
-
1. **기존 코드를 깨지 않는다**: 새 테이블 이름은 ``nodes_v2`` / ``edges_v2``
|
|
22
|
-
로 분리. 기존 ``nodes`` / ``edges`` 와 공존한다. legacy → v2 reprojection 은
|
|
23
|
-
``knowledge_graph.py`` 의 버전 게이트 백필 한 곳에서만 수행한다.
|
|
24
|
-
2. **정규화 + 무손실**: legacy 자유 문자열 타입은 ``NodeType``/``EdgeType``
|
|
25
|
-
superset 으로 정규화해 ``type`` 칼럼에 저장하고, 원본 문자열은 ``legacy_type``
|
|
26
|
-
칼럼에 그대로 보존한다. summary 와 metadata 는 ``attrs._kg`` 패스스루 blob 이
|
|
27
|
-
아니라 전용 ``summary`` 칼럼 / ``attrs``·``metadata`` 칼럼에 1급으로 저장한다.
|
|
28
|
-
3. **표준 라이브러리만 사용**: 외부 의존성 없이 ``sqlite3`` 만으로 동작한다.
|
|
29
|
-
4. **정규화 매핑은 명시적**: 한글 동사/legacy 라벨 → 영문 enum 표가 코드 안에
|
|
30
|
-
들어 있어서 어떤 옛 라벨이 어디로 매핑되는지 한눈에 보인다.
|
|
31
|
-
|
|
32
|
-
사용 예
|
|
33
|
-
-------
|
|
34
|
-
```python
|
|
35
|
-
from lattice_brain.graph.schema import KGStoreV2
|
|
36
|
-
|
|
37
|
-
store = KGStoreV2("/Users/me/.ltcai/kg_v2.db")
|
|
38
|
-
store.init_schema() # nodes_v2 / edges_v2 생성 + 컬럼 drift self-heal
|
|
39
|
-
print(store.stats()) # {"nodes": ..., "by_node_type": {...}, ...}
|
|
40
|
-
```
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
from __future__ import annotations
|
|
44
|
-
|
|
45
|
-
import json
|
|
46
|
-
import logging
|
|
47
|
-
import os
|
|
48
|
-
import sqlite3
|
|
49
|
-
from contextlib import contextmanager
|
|
50
|
-
from enum import Enum
|
|
51
|
-
from pathlib import Path
|
|
52
|
-
from typing import Any, Dict, Optional
|
|
53
|
-
|
|
54
|
-
from ..quiet import quiet
|
|
55
|
-
from ..utils import now_iso as _stamp_now
|
|
56
|
-
|
|
57
|
-
# ── Schema version ──────────────────────────────────────────────────────────
|
|
58
|
-
KG_SCHEMA_V2_VERSION = 2
|
|
59
|
-
EMBED_DIM = int(os.getenv("LATTICEAI_EMBED_DIM", "1024"))
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def set_embed_dim(dim: int) -> None:
|
|
63
|
-
"""Optional way for upper layers to override without direct module import at top level in lattice_brain."""
|
|
64
|
-
global EMBED_DIM
|
|
65
|
-
EMBED_DIM = int(dim) # lattice_brain isolation: no direct latticeai import; callers can pre-set env if using central Config
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# ── Node / Edge taxonomy (PPT 슬라이드 20·21) ──────────────────────────────
|
|
69
|
-
class NodeType(str, Enum):
|
|
70
|
-
"""워크스페이스의 모든 ‘명사’.
|
|
71
|
-
|
|
72
|
-
PPT 슬라이드 20 카탈로그(상단 그룹)에 더해, ``knowledge_graph.py`` 가 실제로
|
|
73
|
-
써오던 legacy 자유 문자열 타입을 **무손실 superset**(하단 그룹)으로 1급 enum 화
|
|
74
|
-
한다. 덕분에 ``from_legacy`` 정규화가 의미를 잃지 않고(예: ``Computer`` →
|
|
75
|
-
``COMPUTER``), 알 수 없는/동적(이벤트) 타입만 ``CONCEPT`` 로 폴백한다.
|
|
76
|
-
원본 문자열은 ``nodes_v2.legacy_type`` 에 그대로 보존되므로 정규화는 항상 무손실.
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
# PPT 슬라이드 20 정식 카탈로그
|
|
80
|
-
CONVERSATION = "CONVERSATION" # 대화 세션 전체
|
|
81
|
-
MESSAGE = "MESSAGE" # 단일 발화
|
|
82
|
-
FILE = "FILE" # 업로드/연결된 파일
|
|
83
|
-
DOCUMENT = "DOCUMENT" # 생성/관리되는 문서 (보고서, 계획서 등)
|
|
84
|
-
CHUNK = "CHUNK" # 파일의 분할 청크
|
|
85
|
-
CODE_SYMBOL = "CODE_SYMBOL" # 함수·클래스·모듈
|
|
86
|
-
CONCEPT = "CONCEPT" # 추출된 개념 / 태그
|
|
87
|
-
PERSON = "PERSON" # 사용자·협업자
|
|
88
|
-
MODEL = "MODEL" # 로컬/원격 LLM
|
|
89
|
-
TOOL = "TOOL" # MCP 서버·외부 도구
|
|
90
|
-
PROJECT = "PROJECT" # 주제별 작업 공간
|
|
91
|
-
# legacy superset — knowledge_graph.py 가 실제로 생성하던 노드 타입들
|
|
92
|
-
COMPUTER = "COMPUTER" # 내 컴퓨터 (로컬 스캔 루트)
|
|
93
|
-
DRIVE = "DRIVE" # 드라이브 / 볼륨
|
|
94
|
-
FOLDER = "FOLDER" # 폴더
|
|
95
|
-
CODE_FILE = "CODE_FILE" # 코드 파일 (.py/.ts 등)
|
|
96
|
-
SPREADSHEET = "SPREADSHEET" # 엑셀 / CSV
|
|
97
|
-
SLIDE_DECK = "SLIDE_DECK" # 프레젠테이션
|
|
98
|
-
IMAGE = "IMAGE" # 이미지 파일
|
|
99
|
-
IMAGE_TEXT = "IMAGE_TEXT" # OCR 텍스트
|
|
100
|
-
SLIDE = "SLIDE" # 슬라이드 (덱의 한 장)
|
|
101
|
-
PAGE = "PAGE" # 페이지 (문서의 한 면)
|
|
102
|
-
SHEET = "SHEET" # 시트 (스프레드시트의 한 탭)
|
|
103
|
-
SECTION = "SECTION" # 문서 섹션
|
|
104
|
-
CHAT = "CHAT" # 대화 세션(채팅 UI)
|
|
105
|
-
AI_RESPONSE = "AI_RESPONSE" # 어시스턴트 발화
|
|
106
|
-
TOPIC = "TOPIC" # 주제 / 토픽
|
|
107
|
-
FEATURE = "FEATURE" # 소프트웨어 기능
|
|
108
|
-
TASK = "TASK" # 할 일
|
|
109
|
-
DECISION = "DECISION" # 결정 사항
|
|
110
|
-
ERROR = "ERROR" # 오류 / 버그
|
|
111
|
-
EVENT = "EVENT" # 분석/시스템 이벤트(동적 타입 폴백)
|
|
112
|
-
# v3.6.0 Knowledge Graph First — 모든 데이터 소스가 그래프로 수렴하기 위한
|
|
113
|
-
# 1급 엔티티. 추가형(additive)·확장 가능(extensible): 새 도메인 엔티티는
|
|
114
|
-
# 여기에 enum 멤버를 추가하고 _LEGACY_NODE_MAP 에 별칭만 등록하면 된다.
|
|
115
|
-
SOURCE = "SOURCE" # 수집 출처(파일/URL/브라우저 탭/git 등)의 출처 노드
|
|
116
|
-
REPOSITORY = "REPOSITORY" # git 저장소
|
|
117
|
-
MEETING = "MEETING" # 회의 / 미팅
|
|
118
|
-
ORGANIZATION = "ORGANIZATION" # 조직 / 회사 / 팀
|
|
119
|
-
WORKFLOW = "WORKFLOW" # 워크플로우 정의/실행
|
|
120
|
-
AGENT = "AGENT" # 에이전트(역할/실행 주체)
|
|
121
|
-
# v11.1.0 Personal Ontology (Track 4) — the Self-Model subgraph. These are
|
|
122
|
-
# not "another kind of document": they are what the Brain has been told
|
|
123
|
-
# about *its owner*, so they are read, edited and deleted as a unit
|
|
124
|
-
# (``lattice_brain.self_model``). DECISION already exists above and is
|
|
125
|
-
# reused rather than duplicated — a decision is a decision whether it came
|
|
126
|
-
# from a meeting note or from "결정: 로컬 모델을 기본값으로 쓴다".
|
|
127
|
-
SELF = "SELF" # 사용자 자신(Self-Model 루트)
|
|
128
|
-
PREFERENCE = "PREFERENCE" # 선호 (좋아함/싫어함/선호 방식)
|
|
129
|
-
HABIT = "HABIT" # 습관 (반복되는 행동)
|
|
130
|
-
RELATIONSHIP = "RELATIONSHIP" # 관계 (동료/친구/멘토 등)
|
|
131
|
-
# v11.1.0 Multi-modal (Track 3) — a recording is its own noun. It used to
|
|
132
|
-
# enter through the text door as a DOCUMENT because "a transcript *is*
|
|
133
|
-
# text"; that is true of the transcript and false of the recording, which
|
|
134
|
-
# exists whether or not anyone could hear it. IMAGE has been first-class
|
|
135
|
-
# since 3.6.0 for the same reason, and AUDIO now sits beside it.
|
|
136
|
-
AUDIO = "AUDIO" # 녹음 / 음성 메모
|
|
137
|
-
# v11.2.0 — a video is its own noun for the same reason a recording is:
|
|
138
|
-
# keyframes and subtitles are *derived* from it (IMAGE children, text
|
|
139
|
-
# chunks), and calling the thing they came from a DOCUMENT would make the
|
|
140
|
-
# source of a memory indistinguishable from one of its pieces.
|
|
141
|
-
VIDEO = "VIDEO" # 영상 / 화면 녹화
|
|
142
|
-
|
|
143
|
-
@classmethod
|
|
144
|
-
def from_legacy(cls, label: str) -> "NodeType":
|
|
145
|
-
"""legacy ``knowledge_graph.py`` 의 자유 문자열을 정식 enum 으로 정규화.
|
|
146
|
-
|
|
147
|
-
매핑이 없는(동적 이벤트 등) 타입은 ``CONCEPT`` 로 폴백하지만, 호출부는
|
|
148
|
-
원본 문자열을 ``legacy_type`` 칼럼에 별도 보존하므로 정보 손실은 없다.
|
|
149
|
-
"""
|
|
150
|
-
m = (label or "").strip()
|
|
151
|
-
# Canonical values round-trip exactly (v4 native writes use them);
|
|
152
|
-
# without this, CODE_FILE/AI_RESPONSE etc. would degrade to CONCEPT.
|
|
153
|
-
try:
|
|
154
|
-
return cls(m.upper())
|
|
155
|
-
except ValueError:
|
|
156
|
-
quiet()
|
|
157
|
-
return _LEGACY_NODE_MAP.get(m.lower(), cls.CONCEPT)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
class EdgeType(str, Enum):
|
|
161
|
-
"""노드 사이의 ‘방향성 있고 타입이 명시된’ 관계. PPT 슬라이드 21."""
|
|
162
|
-
|
|
163
|
-
CONTAINS = "CONTAINS" # FILE → CHUNK
|
|
164
|
-
MENTIONS = "MENTIONS" # MESSAGE → CONCEPT
|
|
165
|
-
REFERENCES = "REFERENCES" # FILE → FILE / URL
|
|
166
|
-
REPLIES_TO = "REPLIES_TO" # MESSAGE → MESSAGE
|
|
167
|
-
AUTHORED_BY = "AUTHORED_BY" # FILE → PERSON
|
|
168
|
-
USES = "USES" # PROJECT → TOOL / MODEL
|
|
169
|
-
DERIVED_FROM = "DERIVED_FROM" # CHUNK → CHUNK (요약 등)
|
|
170
|
-
SIMILAR_TO = "SIMILAR_TO" # ANY ↔ ANY (의미 유사도)
|
|
171
|
-
DEPENDS_ON = "DEPENDS_ON" # CODE_SYMBOL → CODE_SYMBOL
|
|
172
|
-
TAGGED_AS = "TAGGED_AS" # ANY → CONCEPT
|
|
173
|
-
VERSION_OF = "VERSION_OF" # FILE → FILE (히스토리)
|
|
174
|
-
GRANTS_ACCESS = "GRANTS_ACCESS" # PERSON → RESOURCE
|
|
175
|
-
USED_IN = "USED_IN" # CONCEPT → DOCUMENT (문서에 활용됨)
|
|
176
|
-
INSPIRED_BY = "INSPIRED_BY" # DOCUMENT → DOCUMENT (영감/참조 관계)
|
|
177
|
-
CONTRADICTS = "CONTRADICTS" # DOCUMENT ↔ DOCUMENT (상충 관계)
|
|
178
|
-
EVOLVES_FROM = "EVOLVES_FROM" # DOCUMENT → DOCUMENT (발전/개정 관계)
|
|
179
|
-
# legacy superset — knowledge_graph.py 가 실제로 생성하던 엣지 타입들
|
|
180
|
-
UPLOADED_BY = "UPLOADED_BY" # PERSON → FILE (업로드함)
|
|
181
|
-
WROTE = "WROTE" # PERSON → CONVERSATION (작성함)
|
|
182
|
-
HAS_EVENT = "HAS_EVENT" # CONVERSATION → EVENT (has_event)
|
|
183
|
-
TRIGGERED = "TRIGGERED" # PERSON → EVENT (triggered)
|
|
184
|
-
HAS_SLIDE = "HAS_SLIDE" # SLIDE_DECK → SLIDE (has_slide)
|
|
185
|
-
HAS_PAGE = "HAS_PAGE" # DOCUMENT → PAGE (has_page)
|
|
186
|
-
HAS_SHEET = "HAS_SHEET" # SPREADSHEET → SHEET (has_sheet)
|
|
187
|
-
HAS_CHUNK = "HAS_CHUNK" # FILE → CHUNK (has_chunk)
|
|
188
|
-
CONTAINS_IMAGE = "CONTAINS_IMAGE" # FILE → IMAGE (contains_image)
|
|
189
|
-
CONTAINS_SIGNAL = "CONTAINS_SIGNAL" # NODE → CONCEPT (contains_signal)
|
|
190
|
-
DISCUSSES = "DISCUSSES" # SLIDE/PAGE → TOPIC (discusses)
|
|
191
|
-
IMPLIES = "IMPLIES" # NODE → NODE (implies)
|
|
192
|
-
RELATED_TO = "RELATED_TO" # ANY ↔ ANY (related_to)
|
|
193
|
-
# v3.6.0 Knowledge Graph First — 출처/소유/구성/결정 관계를 1급 엣지로 승격.
|
|
194
|
-
# 추가형: 새 관계는 enum 멤버 추가 + _LEGACY_EDGE_MAP 별칭 등록만으로 확장된다.
|
|
195
|
-
INDEXED_FROM = "INDEXED_FROM" # NODE → SOURCE (어떤 출처에서 색인됐는가)
|
|
196
|
-
MODIFIED_BY = "MODIFIED_BY" # NODE → PERSON (마지막 수정자)
|
|
197
|
-
BELONGS_TO_PROJECT = "BELONGS_TO_PROJECT" # NODE → PROJECT
|
|
198
|
-
PART_OF = "PART_OF" # NODE → NODE (구성요소 관계)
|
|
199
|
-
DISCUSSED_IN = "DISCUSSED_IN" # CONCEPT/DECISION → MEETING/CHAT
|
|
200
|
-
DECIDED_BY = "DECIDED_BY" # DECISION → PERSON
|
|
201
|
-
GENERATED_BY = "GENERATED_BY" # NODE → AGENT/MODEL/WORKFLOW
|
|
202
|
-
USED_BY_AGENT = "USED_BY_AGENT" # NODE → AGENT (에이전트가 사용함)
|
|
203
|
-
|
|
204
|
-
@classmethod
|
|
205
|
-
def from_legacy(cls, label: str) -> "EdgeType":
|
|
206
|
-
"""legacy 자유 문자열/한글 동사를 정식 enum 으로 정규화.
|
|
207
|
-
|
|
208
|
-
매핑이 없는 동적 타입은 ``MENTIONS`` 로 폴백하지만, 호출부는 원본 문자열을
|
|
209
|
-
``edges_v2.legacy_type`` 에 보존하므로 정보 손실은 없다.
|
|
210
|
-
"""
|
|
211
|
-
m = (label or "").strip()
|
|
212
|
-
# Canonical values round-trip exactly (v4 native writes use them).
|
|
213
|
-
try:
|
|
214
|
-
return cls(m.upper())
|
|
215
|
-
except ValueError:
|
|
216
|
-
quiet()
|
|
217
|
-
return _LEGACY_EDGE_MAP.get(m.lower(), cls.MENTIONS)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
# legacy(자유 문자열 / 한글 동사) → enum 매핑 표.
|
|
221
|
-
# superset 정규화: 알려진 legacy 타입은 1:1 의미 보존 매핑, 미지/동적 타입만 폴백.
|
|
222
|
-
_LEGACY_NODE_MAP: Dict[str, NodeType] = {
|
|
223
|
-
"conversation": NodeType.CONVERSATION,
|
|
224
|
-
"chat": NodeType.CHAT,
|
|
225
|
-
"message": NodeType.MESSAGE,
|
|
226
|
-
"airesponse": NodeType.AI_RESPONSE,
|
|
227
|
-
"file": NodeType.FILE,
|
|
228
|
-
"codefile": NodeType.CODE_FILE,
|
|
229
|
-
"spreadsheet": NodeType.SPREADSHEET,
|
|
230
|
-
"slidedeck": NodeType.SLIDE_DECK,
|
|
231
|
-
"image": NodeType.IMAGE,
|
|
232
|
-
"imagetext": NodeType.IMAGE_TEXT,
|
|
233
|
-
"computer": NodeType.COMPUTER,
|
|
234
|
-
"drive": NodeType.DRIVE,
|
|
235
|
-
"folder": NodeType.FOLDER,
|
|
236
|
-
"page": NodeType.PAGE,
|
|
237
|
-
"sheet": NodeType.SHEET,
|
|
238
|
-
"slide": NodeType.SLIDE,
|
|
239
|
-
"section": NodeType.SECTION,
|
|
240
|
-
"chunk": NodeType.CHUNK,
|
|
241
|
-
"code": NodeType.CODE_SYMBOL,
|
|
242
|
-
"concept": NodeType.CONCEPT,
|
|
243
|
-
"topic": NodeType.TOPIC,
|
|
244
|
-
"feature": NodeType.FEATURE,
|
|
245
|
-
"task": NodeType.TASK,
|
|
246
|
-
"decision": NodeType.DECISION,
|
|
247
|
-
"error": NodeType.ERROR,
|
|
248
|
-
"event": NodeType.EVENT,
|
|
249
|
-
"tag": NodeType.CONCEPT,
|
|
250
|
-
"person": NodeType.PERSON,
|
|
251
|
-
"user": NodeType.PERSON,
|
|
252
|
-
"model": NodeType.MODEL,
|
|
253
|
-
"tool": NodeType.TOOL,
|
|
254
|
-
"mcp": NodeType.TOOL,
|
|
255
|
-
"project": NodeType.PROJECT,
|
|
256
|
-
"workspace": NodeType.PROJECT,
|
|
257
|
-
"document": NodeType.DOCUMENT,
|
|
258
|
-
"report": NodeType.DOCUMENT,
|
|
259
|
-
"plan": NodeType.DOCUMENT,
|
|
260
|
-
"proposal": NodeType.DOCUMENT,
|
|
261
|
-
"보고서": NodeType.DOCUMENT,
|
|
262
|
-
"계획서": NodeType.DOCUMENT,
|
|
263
|
-
"기획서": NodeType.DOCUMENT,
|
|
264
|
-
# v3.6.0 Knowledge Graph First 엔티티
|
|
265
|
-
"source": NodeType.SOURCE,
|
|
266
|
-
"ingestionsource": NodeType.SOURCE,
|
|
267
|
-
"repository": NodeType.REPOSITORY,
|
|
268
|
-
"repo": NodeType.REPOSITORY,
|
|
269
|
-
"gitrepo": NodeType.REPOSITORY,
|
|
270
|
-
"meeting": NodeType.MEETING,
|
|
271
|
-
"organization": NodeType.ORGANIZATION,
|
|
272
|
-
"org": NodeType.ORGANIZATION,
|
|
273
|
-
"company": NodeType.ORGANIZATION,
|
|
274
|
-
"team": NodeType.ORGANIZATION,
|
|
275
|
-
"workflow": NodeType.WORKFLOW,
|
|
276
|
-
"agent": NodeType.AGENT,
|
|
277
|
-
# v11.1.0 Personal Ontology — the Self-Model vocabulary, including the
|
|
278
|
-
# Korean labels a person actually types.
|
|
279
|
-
"self": NodeType.SELF,
|
|
280
|
-
"selfmodel": NodeType.SELF,
|
|
281
|
-
"me": NodeType.SELF,
|
|
282
|
-
"나": NodeType.SELF,
|
|
283
|
-
"preference": NodeType.PREFERENCE,
|
|
284
|
-
"선호": NodeType.PREFERENCE,
|
|
285
|
-
"habit": NodeType.HABIT,
|
|
286
|
-
"습관": NodeType.HABIT,
|
|
287
|
-
"relationship": NodeType.RELATIONSHIP,
|
|
288
|
-
"관계": NodeType.RELATIONSHIP,
|
|
289
|
-
"결정": NodeType.DECISION,
|
|
290
|
-
# v11.1.0 Multi-modal — recordings as a first-class noun.
|
|
291
|
-
"audio": NodeType.AUDIO,
|
|
292
|
-
"오디오": NodeType.AUDIO,
|
|
293
|
-
# v11.2.0 — videos likewise.
|
|
294
|
-
"video": NodeType.VIDEO,
|
|
295
|
-
"영상": NodeType.VIDEO,
|
|
296
|
-
"동영상": NodeType.VIDEO,
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
_LEGACY_EDGE_MAP: Dict[str, EdgeType] = {
|
|
300
|
-
# 한글 동사 (knowledge_graph.py 의 EDGE_VERB)
|
|
301
|
-
"언급함": EdgeType.MENTIONS,
|
|
302
|
-
"포함함": EdgeType.CONTAINS,
|
|
303
|
-
"해결함": EdgeType.REFERENCES,
|
|
304
|
-
"의존함": EdgeType.DEPENDS_ON,
|
|
305
|
-
"설명함": EdgeType.MENTIONS,
|
|
306
|
-
"비교함": EdgeType.SIMILAR_TO,
|
|
307
|
-
"사용함": EdgeType.USES,
|
|
308
|
-
"연결함": EdgeType.REFERENCES,
|
|
309
|
-
"확장함": EdgeType.DERIVED_FROM,
|
|
310
|
-
"생성함": EdgeType.AUTHORED_BY,
|
|
311
|
-
"작성함": EdgeType.WROTE,
|
|
312
|
-
"업로드함": EdgeType.UPLOADED_BY,
|
|
313
|
-
"대체함": EdgeType.VERSION_OF,
|
|
314
|
-
"지원함": EdgeType.USES,
|
|
315
|
-
"발생함": EdgeType.REFERENCES,
|
|
316
|
-
"관련됨": EdgeType.MENTIONS,
|
|
317
|
-
# 영문 별칭
|
|
318
|
-
"mentions": EdgeType.MENTIONS,
|
|
319
|
-
"contains": EdgeType.CONTAINS,
|
|
320
|
-
"references": EdgeType.REFERENCES,
|
|
321
|
-
"replies_to": EdgeType.REPLIES_TO,
|
|
322
|
-
"authored_by": EdgeType.AUTHORED_BY,
|
|
323
|
-
"uses": EdgeType.USES,
|
|
324
|
-
"derived_from": EdgeType.DERIVED_FROM,
|
|
325
|
-
"similar_to": EdgeType.SIMILAR_TO,
|
|
326
|
-
"depends_on": EdgeType.DEPENDS_ON,
|
|
327
|
-
"tagged_as": EdgeType.TAGGED_AS,
|
|
328
|
-
"version_of": EdgeType.VERSION_OF,
|
|
329
|
-
"grants_access": EdgeType.GRANTS_ACCESS,
|
|
330
|
-
"used_in": EdgeType.USED_IN,
|
|
331
|
-
"inspired_by": EdgeType.INSPIRED_BY,
|
|
332
|
-
"contradicts": EdgeType.CONTRADICTS,
|
|
333
|
-
"evolves_from": EdgeType.EVOLVES_FROM,
|
|
334
|
-
# legacy superset 별칭 (knowledge_graph.py 가 실제로 쓰던 엣지 타입)
|
|
335
|
-
"uploaded_by": EdgeType.UPLOADED_BY,
|
|
336
|
-
"wrote": EdgeType.WROTE,
|
|
337
|
-
"has_event": EdgeType.HAS_EVENT,
|
|
338
|
-
"triggered": EdgeType.TRIGGERED,
|
|
339
|
-
"has_slide": EdgeType.HAS_SLIDE,
|
|
340
|
-
"has_page": EdgeType.HAS_PAGE,
|
|
341
|
-
"has_sheet": EdgeType.HAS_SHEET,
|
|
342
|
-
"has_chunk": EdgeType.HAS_CHUNK,
|
|
343
|
-
"contains_image": EdgeType.CONTAINS_IMAGE,
|
|
344
|
-
"contains_signal": EdgeType.CONTAINS_SIGNAL,
|
|
345
|
-
"discusses": EdgeType.DISCUSSES,
|
|
346
|
-
"implies": EdgeType.IMPLIES,
|
|
347
|
-
"related_to": EdgeType.RELATED_TO,
|
|
348
|
-
"활용됨": EdgeType.USED_IN,
|
|
349
|
-
"영감받음": EdgeType.INSPIRED_BY,
|
|
350
|
-
"상충함": EdgeType.CONTRADICTS,
|
|
351
|
-
"발전함": EdgeType.EVOLVES_FROM,
|
|
352
|
-
# v3.6.0 Knowledge Graph First 관계
|
|
353
|
-
"indexed_from": EdgeType.INDEXED_FROM,
|
|
354
|
-
"modified_by": EdgeType.MODIFIED_BY,
|
|
355
|
-
"belongs_to_project": EdgeType.BELONGS_TO_PROJECT,
|
|
356
|
-
"belongs_to": EdgeType.BELONGS_TO_PROJECT,
|
|
357
|
-
"part_of": EdgeType.PART_OF,
|
|
358
|
-
"discussed_in": EdgeType.DISCUSSED_IN,
|
|
359
|
-
"decided_by": EdgeType.DECIDED_BY,
|
|
360
|
-
"generated_by": EdgeType.GENERATED_BY,
|
|
361
|
-
"used_by_agent": EdgeType.USED_BY_AGENT,
|
|
362
|
-
"색인됨": EdgeType.INDEXED_FROM,
|
|
363
|
-
"수정함": EdgeType.MODIFIED_BY,
|
|
364
|
-
"결정함": EdgeType.DECIDED_BY,
|
|
365
|
-
"구성요소": EdgeType.PART_OF,
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
# ── SQLite v2 store ─────────────────────────────────────────────────────────
|
|
369
|
-
SCHEMA_SQL = """
|
|
370
|
-
CREATE TABLE IF NOT EXISTS kg_meta (
|
|
371
|
-
key TEXT PRIMARY KEY,
|
|
372
|
-
value TEXT NOT NULL
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
CREATE TABLE IF NOT EXISTS nodes_v2 (
|
|
376
|
-
id TEXT PRIMARY KEY,
|
|
377
|
-
type TEXT NOT NULL,
|
|
378
|
-
legacy_type TEXT,
|
|
379
|
-
label TEXT NOT NULL,
|
|
380
|
-
summary TEXT,
|
|
381
|
-
attrs TEXT NOT NULL DEFAULT '{}',
|
|
382
|
-
embedding BLOB,
|
|
383
|
-
owner_id TEXT,
|
|
384
|
-
-- NULL workspace_id = legacy-global (pre-scoping rows, readable machine-wide).
|
|
385
|
-
workspace_id TEXT,
|
|
386
|
-
-- 'legacy' marks rows that predate scoping — the 'private' default must not
|
|
387
|
-
-- silently privatize previously machine-shared data (design-review ruling).
|
|
388
|
-
visibility TEXT NOT NULL DEFAULT 'private',
|
|
389
|
-
-- Revision chain: a node replaced by a newer one points at its successor.
|
|
390
|
-
superseded_by TEXT,
|
|
391
|
-
-- Bitemporal validity (v11.1.0). NULL is the convention, not '':
|
|
392
|
-
-- valid_from IS NULL → the row has been valid since created_at
|
|
393
|
-
-- valid_to IS NULL → the row is still valid now
|
|
394
|
-
-- An empty string would read as "valid since the beginning of time" in a
|
|
395
|
-
-- string comparison, which is why the columns are nullable with no default.
|
|
396
|
-
valid_from TEXT,
|
|
397
|
-
valid_to TEXT,
|
|
398
|
-
created_at TEXT NOT NULL,
|
|
399
|
-
updated_at TEXT NOT NULL,
|
|
400
|
-
style TEXT,
|
|
401
|
-
tone TEXT,
|
|
402
|
-
importance_score REAL NOT NULL DEFAULT 0.0,
|
|
403
|
-
last_used TEXT
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
CREATE TABLE IF NOT EXISTS edges_v2 (
|
|
407
|
-
id TEXT PRIMARY KEY,
|
|
408
|
-
source TEXT NOT NULL,
|
|
409
|
-
target TEXT NOT NULL,
|
|
410
|
-
type TEXT NOT NULL,
|
|
411
|
-
legacy_type TEXT NOT NULL DEFAULT '',
|
|
412
|
-
weight REAL NOT NULL DEFAULT 1.0,
|
|
413
|
-
confidence REAL NOT NULL DEFAULT 1.0,
|
|
414
|
-
evidence TEXT NOT NULL DEFAULT '[]',
|
|
415
|
-
metadata TEXT NOT NULL DEFAULT '{}',
|
|
416
|
-
created_by TEXT NOT NULL DEFAULT 'user',
|
|
417
|
-
created_at TEXT NOT NULL,
|
|
418
|
-
-- Bitemporal validity + revision chain (v11.1.0), same NULL convention as
|
|
419
|
-
-- nodes_v2: NULL valid_from falls back to created_at, NULL valid_to means
|
|
420
|
-
-- "still holds". Relationships go stale exactly like facts do.
|
|
421
|
-
valid_from TEXT,
|
|
422
|
-
valid_to TEXT,
|
|
423
|
-
superseded_by TEXT,
|
|
424
|
-
-- Edge identity (v4): the normalized type AND the raw legacy type.
|
|
425
|
-
-- Migrated rows keep their legacy_type discriminator, so two distinct
|
|
426
|
-
-- legacy strings between one pair (e.g. "mentions" / "관련됨") stay
|
|
427
|
-
-- distinct even though both normalize to MENTIONS. Native canonical
|
|
428
|
-
-- writes carry legacy_type='' so their identity is effectively
|
|
429
|
-
-- (source, target, type) — two canonical types between the same pair
|
|
430
|
-
-- (e.g. MENTIONS + CONTAINS) never collide. The pre-v4
|
|
431
|
-
-- UNIQUE(source, target, legacy_type) would have silently merged them.
|
|
432
|
-
UNIQUE(source, target, type, legacy_type),
|
|
433
|
-
FOREIGN KEY(source) REFERENCES nodes_v2(id) ON DELETE CASCADE,
|
|
434
|
-
FOREIGN KEY(target) REFERENCES nodes_v2(id) ON DELETE CASCADE
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
-- Temporal dimension (v4): every repeated observation of a relationship is
|
|
438
|
-
-- recorded — edges_v2's UNIQUE identity + weight=max would otherwise erase
|
|
439
|
-
-- when something was learned, how often, and whether it still holds.
|
|
440
|
-
CREATE TABLE IF NOT EXISTS edge_occurrences (
|
|
441
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
442
|
-
edge_id TEXT NOT NULL,
|
|
443
|
-
observed_at TEXT NOT NULL,
|
|
444
|
-
weight REAL NOT NULL DEFAULT 1.0,
|
|
445
|
-
source TEXT,
|
|
446
|
-
FOREIGN KEY(edge_id) REFERENCES edges_v2(id) ON DELETE CASCADE
|
|
447
|
-
);
|
|
448
|
-
CREATE INDEX IF NOT EXISTS idx_edge_occurrences_edge ON edge_occurrences(edge_id);
|
|
449
|
-
CREATE INDEX IF NOT EXISTS idx_edge_occurrences_time ON edge_occurrences(observed_at);
|
|
450
|
-
|
|
451
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_v2_type ON nodes_v2(type);
|
|
452
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_v2_legacy ON nodes_v2(legacy_type);
|
|
453
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_v2_owner ON nodes_v2(owner_id);
|
|
454
|
-
CREATE INDEX IF NOT EXISTS idx_edges_v2_source ON edges_v2(source);
|
|
455
|
-
CREATE INDEX IF NOT EXISTS idx_edges_v2_target ON edges_v2(target);
|
|
456
|
-
CREATE INDEX IF NOT EXISTS idx_edges_v2_type ON edges_v2(type);
|
|
457
|
-
CREATE INDEX IF NOT EXISTS idx_edges_v2_legacy ON edges_v2(legacy_type);
|
|
458
|
-
"""
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
# ── Temporal read convention (v11.1.0) ──────────────────────────────────────
|
|
462
|
-
# One predicate, shared by every ``as_of`` read so the fallback rule cannot
|
|
463
|
-
# drift between call sites:
|
|
464
|
-
# * ``valid_from IS NULL`` → the row has been valid since ``created_at``
|
|
465
|
-
# (plan §10 mitigation: existing Brains keep working with no backfill)
|
|
466
|
-
# * ``valid_to IS NULL`` → the row is still valid
|
|
467
|
-
# The window is half-open ``[valid_from, valid_to)``: the instant a fact is
|
|
468
|
-
# superseded belongs to its successor, so an ``as_of`` at exactly that instant
|
|
469
|
-
# returns exactly one of the two.
|
|
470
|
-
TEMPORAL_PREDICATE_SQL = (
|
|
471
|
-
"COALESCE(valid_from, created_at) <= ? AND (valid_to IS NULL OR valid_to > ?)"
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
def _exec_script(conn: sqlite3.Connection, script: str) -> None:
|
|
476
|
-
"""Run a multi-statement SQL script on ``conn`` statement-by-statement.
|
|
477
|
-
|
|
478
|
-
Unlike ``sqlite3.Connection.executescript``, this does NOT issue an implicit
|
|
479
|
-
COMMIT before running, so the statements join the caller's open transaction.
|
|
480
|
-
Safe for our schema/view DDL (no ``;`` inside string literals).
|
|
481
|
-
"""
|
|
482
|
-
for stmt in script.split(";"):
|
|
483
|
-
s = stmt.strip()
|
|
484
|
-
if s:
|
|
485
|
-
conn.execute(s)
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
class KGStoreV2:
|
|
489
|
-
"""가벼운 SQLite 기반 v2 스토어 — **스키마/초기화 지원 전용**.
|
|
490
|
-
|
|
491
|
-
``init_schema`` 으로 ``nodes_v2``/``edges_v2`` 를 생성·heal 하고 ``stats`` 로
|
|
492
|
-
집계를 노출한다. 데이터 read/write 는 ``knowledge_graph.KnowledgeGraphStore``
|
|
493
|
-
프로젝션이 담당하므로 native upsert/get/search API 는 두지 않는다.
|
|
494
|
-
"""
|
|
495
|
-
|
|
496
|
-
def __init__(self, db_path: "str | Path"):
|
|
497
|
-
# Callers pass a Path (the store keeps db_path as a Path); sqlite3
|
|
498
|
-
# accepts either, so the annotation now matches what is actually
|
|
499
|
-
# handed in instead of forcing every call site to str() it.
|
|
500
|
-
self.db_path = str(db_path)
|
|
501
|
-
|
|
502
|
-
@contextmanager
|
|
503
|
-
def _conn(self):
|
|
504
|
-
conn = sqlite3.connect(self.db_path)
|
|
505
|
-
conn.row_factory = sqlite3.Row
|
|
506
|
-
conn.execute("PRAGMA foreign_keys = ON")
|
|
507
|
-
try:
|
|
508
|
-
yield conn
|
|
509
|
-
conn.commit()
|
|
510
|
-
finally:
|
|
511
|
-
conn.close()
|
|
512
|
-
|
|
513
|
-
# Columns the current code writes; used to detect schema-evolution drift in
|
|
514
|
-
# v2 tables that an older ``CREATE TABLE IF NOT EXISTS`` left behind.
|
|
515
|
-
_V2_EXPECTED_COLUMNS = {
|
|
516
|
-
"edges_v2": {
|
|
517
|
-
"id",
|
|
518
|
-
"source",
|
|
519
|
-
"target",
|
|
520
|
-
"type",
|
|
521
|
-
"legacy_type",
|
|
522
|
-
"weight",
|
|
523
|
-
"confidence",
|
|
524
|
-
"evidence",
|
|
525
|
-
"metadata",
|
|
526
|
-
"created_by",
|
|
527
|
-
"created_at",
|
|
528
|
-
"valid_from",
|
|
529
|
-
"valid_to",
|
|
530
|
-
"superseded_by",
|
|
531
|
-
},
|
|
532
|
-
"nodes_v2": {
|
|
533
|
-
"id",
|
|
534
|
-
"type",
|
|
535
|
-
"legacy_type",
|
|
536
|
-
"label",
|
|
537
|
-
"summary",
|
|
538
|
-
"attrs",
|
|
539
|
-
"embedding",
|
|
540
|
-
"owner_id",
|
|
541
|
-
"workspace_id",
|
|
542
|
-
"visibility",
|
|
543
|
-
"superseded_by",
|
|
544
|
-
"valid_from",
|
|
545
|
-
"valid_to",
|
|
546
|
-
"created_at",
|
|
547
|
-
"updated_at",
|
|
548
|
-
"style",
|
|
549
|
-
"tone",
|
|
550
|
-
"importance_score",
|
|
551
|
-
"last_used",
|
|
552
|
-
},
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
# Columns added after a table's first release that can be healed in place
|
|
556
|
-
# with ALTER TABLE ADD COLUMN (nullable / defaulted only).
|
|
557
|
-
_V2_ADDABLE_COLUMNS = {
|
|
558
|
-
"nodes_v2": {
|
|
559
|
-
"workspace_id": "TEXT",
|
|
560
|
-
"superseded_by": "TEXT",
|
|
561
|
-
"valid_from": "TEXT",
|
|
562
|
-
"valid_to": "TEXT",
|
|
563
|
-
},
|
|
564
|
-
"edges_v2": {
|
|
565
|
-
"valid_from": "TEXT",
|
|
566
|
-
"valid_to": "TEXT",
|
|
567
|
-
"superseded_by": "TEXT",
|
|
568
|
-
},
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
def _drop_stale_empty_v2_tables(self, conn: sqlite3.Connection) -> None:
|
|
572
|
-
"""Drop v2 tables that predate a schema change — but only when empty.
|
|
573
|
-
|
|
574
|
-
``CREATE TABLE IF NOT EXISTS`` never upgrades an existing table, so a
|
|
575
|
-
v2 table created by an older version keeps its old columns and breaks
|
|
576
|
-
inserts. Recreating is safe precisely because these tables have never
|
|
577
|
-
held data (the v2 read-path isn't wired yet); we refuse to drop any
|
|
578
|
-
table that contains rows.
|
|
579
|
-
"""
|
|
580
|
-
# edges_v2 first (it has FKs into nodes_v2)
|
|
581
|
-
for table in ("edges_v2", "nodes_v2"):
|
|
582
|
-
exists = conn.execute(
|
|
583
|
-
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (table,)
|
|
584
|
-
).fetchone()
|
|
585
|
-
if not exists:
|
|
586
|
-
continue
|
|
587
|
-
cols = {
|
|
588
|
-
r[1] for r in conn.execute(f"PRAGMA table_info({table})").fetchall()
|
|
589
|
-
}
|
|
590
|
-
missing = self._V2_EXPECTED_COLUMNS[table] - cols
|
|
591
|
-
if not missing:
|
|
592
|
-
continue
|
|
593
|
-
# Additive columns heal in place without touching data.
|
|
594
|
-
addable = self._V2_ADDABLE_COLUMNS.get(table, {})
|
|
595
|
-
for col in sorted(missing & set(addable)):
|
|
596
|
-
conn.execute(f"ALTER TABLE {table} ADD COLUMN {col} {addable[col]}")
|
|
597
|
-
missing -= set(addable)
|
|
598
|
-
if not missing:
|
|
599
|
-
continue
|
|
600
|
-
count = conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
|
|
601
|
-
if count == 0:
|
|
602
|
-
conn.execute(f"DROP TABLE {table}")
|
|
603
|
-
else:
|
|
604
|
-
logging.warning(
|
|
605
|
-
"kg_schema: %s is missing columns %s but holds %d rows — "
|
|
606
|
-
"leaving it untouched (manual migration required).",
|
|
607
|
-
table,
|
|
608
|
-
sorted(missing),
|
|
609
|
-
count,
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
def init_schema(self, conn: Optional[sqlite3.Connection] = None) -> None:
|
|
613
|
-
"""Create the v2 schema and record metadata.
|
|
614
|
-
|
|
615
|
-
Pass ``conn`` to run inside the caller's open transaction (used by the
|
|
616
|
-
atomic knowledge_graph migration); otherwise a private connection is
|
|
617
|
-
opened and committed. Uses ``_exec_script`` rather than
|
|
618
|
-
``executescript`` so it never force-commits the caller's transaction.
|
|
619
|
-
"""
|
|
620
|
-
if conn is not None:
|
|
621
|
-
self._init_schema_on(conn)
|
|
622
|
-
return
|
|
623
|
-
with self._conn() as own:
|
|
624
|
-
self._init_schema_on(own)
|
|
625
|
-
|
|
626
|
-
def _rebuild_edges_identity(self, conn: sqlite3.Connection) -> None:
|
|
627
|
-
"""Migrate edges_v2 from the pre-v4 UNIQUE(source, target, legacy_type)
|
|
628
|
-
identity to UNIQUE(source, target, type, legacy_type).
|
|
629
|
-
|
|
630
|
-
SQLite cannot alter constraints, so this is a create→copy→swap inside
|
|
631
|
-
the caller's transaction. Data-preserving: every existing row keeps its
|
|
632
|
-
legacy_type discriminator. Re-entrant: keyed on the actual constraint
|
|
633
|
-
in sqlite_master, not a one-time stamp.
|
|
634
|
-
"""
|
|
635
|
-
row = conn.execute(
|
|
636
|
-
"SELECT sql FROM sqlite_master WHERE type='table' AND name='edges_v2'"
|
|
637
|
-
).fetchone()
|
|
638
|
-
if not row or "UNIQUE(source, target, type, legacy_type)" in (row["sql"] or ""):
|
|
639
|
-
return
|
|
640
|
-
conn.execute("ALTER TABLE edges_v2 RENAME TO edges_v2_old")
|
|
641
|
-
# Recreate from the canonical DDL (edges_v2 portion of SCHEMA_SQL).
|
|
642
|
-
start = SCHEMA_SQL.index("CREATE TABLE IF NOT EXISTS edges_v2")
|
|
643
|
-
end = SCHEMA_SQL.index(");", start) + 2
|
|
644
|
-
conn.execute(SCHEMA_SQL[start:end].rstrip(";"))
|
|
645
|
-
conn.execute(
|
|
646
|
-
"""
|
|
647
|
-
INSERT INTO edges_v2 (id, source, target, type, legacy_type, weight,
|
|
648
|
-
confidence, evidence, metadata, created_by, created_at)
|
|
649
|
-
SELECT id, source, target, type, legacy_type, weight,
|
|
650
|
-
confidence, evidence, metadata, created_by, created_at
|
|
651
|
-
FROM edges_v2_old
|
|
652
|
-
"""
|
|
653
|
-
)
|
|
654
|
-
conn.execute("DROP TABLE edges_v2_old")
|
|
655
|
-
logging.info(
|
|
656
|
-
"kg_schema: rebuilt edges_v2 with (source, target, type, legacy_type) identity"
|
|
657
|
-
)
|
|
658
|
-
|
|
659
|
-
def _init_schema_on(self, conn: sqlite3.Connection) -> None:
|
|
660
|
-
self._drop_stale_empty_v2_tables(conn)
|
|
661
|
-
self._rebuild_edges_identity(conn)
|
|
662
|
-
_exec_script(conn, SCHEMA_SQL)
|
|
663
|
-
conn.execute(
|
|
664
|
-
"INSERT OR REPLACE INTO kg_meta(key, value) VALUES (?, ?)",
|
|
665
|
-
("schema_version", str(KG_SCHEMA_V2_VERSION)),
|
|
666
|
-
)
|
|
667
|
-
conn.execute(
|
|
668
|
-
"INSERT OR REPLACE INTO kg_meta(key, value) VALUES (?, ?)",
|
|
669
|
-
("embed_dim", str(EMBED_DIM)),
|
|
670
|
-
)
|
|
671
|
-
|
|
672
|
-
# ── Temporal stamping (v11.1.0) ──────────────────────────
|
|
673
|
-
#
|
|
674
|
-
# These are *primitives*, not a policy: the only caller in the product is
|
|
675
|
-
# the review-approval path in :mod:`lattice_brain.synthesis`. Background
|
|
676
|
-
# jobs and agents never call them directly — they raise a proposal and the
|
|
677
|
-
# user approves it. See ``ARCHITECTURE.md`` → "Temporal knowledge".
|
|
678
|
-
def stamp_node_validity(
|
|
679
|
-
self,
|
|
680
|
-
node_id: str,
|
|
681
|
-
*,
|
|
682
|
-
valid_from: Optional[str] = None,
|
|
683
|
-
valid_to: Optional[str] = None,
|
|
684
|
-
superseded_by: Optional[str] = None,
|
|
685
|
-
) -> bool:
|
|
686
|
-
"""Stamp a node's validity window. ``True`` when a row was updated.
|
|
687
|
-
|
|
688
|
-
Only the arguments actually supplied are written; ``None`` leaves the
|
|
689
|
-
stored value alone (it never clears a column back to NULL, so a second
|
|
690
|
-
resolution cannot silently un-supersede an earlier one).
|
|
691
|
-
"""
|
|
692
|
-
return self._stamp_row(
|
|
693
|
-
"nodes_v2",
|
|
694
|
-
node_id,
|
|
695
|
-
{
|
|
696
|
-
"valid_from": valid_from,
|
|
697
|
-
"valid_to": valid_to,
|
|
698
|
-
"superseded_by": superseded_by,
|
|
699
|
-
},
|
|
700
|
-
)
|
|
701
|
-
|
|
702
|
-
def touch_node(self, node_id: str, *, at: Optional[str] = None) -> bool:
|
|
703
|
-
"""Record one access: bump ``importance_score`` and set ``last_used``.
|
|
704
|
-
|
|
705
|
-
Read-path bookkeeping for the importance/decay report. Returns whether
|
|
706
|
-
a row existed; callers treat a ``False``/failure as harmless.
|
|
707
|
-
"""
|
|
708
|
-
with self._conn() as conn:
|
|
709
|
-
cursor = conn.execute(
|
|
710
|
-
"UPDATE nodes_v2 SET importance_score = importance_score + 1.0, "
|
|
711
|
-
"last_used = ? WHERE id = ?",
|
|
712
|
-
(at or _stamp_now(), str(node_id)),
|
|
713
|
-
)
|
|
714
|
-
return int(cursor.rowcount or 0) > 0
|
|
715
|
-
|
|
716
|
-
def _stamp_row(
|
|
717
|
-
self, table: str, row_id: str, updates: Dict[str, Optional[str]]
|
|
718
|
-
) -> bool:
|
|
719
|
-
fields = {key: value for key, value in updates.items() if value is not None}
|
|
720
|
-
if not fields:
|
|
721
|
-
return False
|
|
722
|
-
assignments = ", ".join(f"{key}=?" for key in fields)
|
|
723
|
-
with self._conn() as conn:
|
|
724
|
-
cursor = conn.execute(
|
|
725
|
-
f"UPDATE {table} SET {assignments} WHERE id=?",
|
|
726
|
-
(*fields.values(), str(row_id)),
|
|
727
|
-
)
|
|
728
|
-
return int(cursor.rowcount or 0) > 0
|
|
729
|
-
|
|
730
|
-
# ── Maintenance ──────────────────────────────────────────
|
|
731
|
-
def stats(self) -> Dict[str, Any]:
|
|
732
|
-
with self._conn() as conn:
|
|
733
|
-
n_nodes = conn.execute("SELECT COUNT(*) FROM nodes_v2").fetchone()[0]
|
|
734
|
-
n_edges = conn.execute("SELECT COUNT(*) FROM edges_v2").fetchone()[0]
|
|
735
|
-
per_type = {
|
|
736
|
-
r["type"]: r["c"]
|
|
737
|
-
for r in conn.execute(
|
|
738
|
-
"SELECT type, COUNT(*) AS c FROM nodes_v2 GROUP BY type"
|
|
739
|
-
).fetchall()
|
|
740
|
-
}
|
|
741
|
-
per_edge = {
|
|
742
|
-
r["type"]: r["c"]
|
|
743
|
-
for r in conn.execute(
|
|
744
|
-
"SELECT type, COUNT(*) AS c FROM edges_v2 GROUP BY type"
|
|
745
|
-
).fetchall()
|
|
746
|
-
}
|
|
747
|
-
return {
|
|
748
|
-
"schema_version": KG_SCHEMA_V2_VERSION,
|
|
749
|
-
"embed_dim": EMBED_DIM,
|
|
750
|
-
"nodes": n_nodes,
|
|
751
|
-
"edges": n_edges,
|
|
752
|
-
"by_node_type": per_type,
|
|
753
|
-
"by_edge_type": per_edge,
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
# NOTE: legacy → v2 reprojection lives in ``knowledge_graph.py``
|
|
758
|
-
# (``KnowledgeGraphStore._backfill_v2_if_needed`` / ``_v2_project_node``/_edge),
|
|
759
|
-
# which is the single live, version-gated migration path. The old standalone
|
|
760
|
-
# ``migrate_legacy_to_v2()`` helper + CLI ``migrate`` subcommand were removed as
|
|
761
|
-
# dead code (no callers); the normalized projection now writes the first-class
|
|
762
|
-
# ``legacy_type``/``summary``/``metadata`` columns directly.
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
# ── CLI ────────────────────────────────────────────────────────────────────
|
|
766
|
-
def _cli() -> int:
|
|
767
|
-
import argparse
|
|
768
|
-
|
|
769
|
-
p = argparse.ArgumentParser(
|
|
770
|
-
prog="kg_schema", description="Lattice AI KG v2 utilities"
|
|
771
|
-
)
|
|
772
|
-
sub = p.add_subparsers(dest="cmd", required=True)
|
|
773
|
-
|
|
774
|
-
sub_init = sub.add_parser("init", help="initialize v2 schema in a DB")
|
|
775
|
-
sub_init.add_argument("db", help="path to sqlite db")
|
|
776
|
-
|
|
777
|
-
sub_stats = sub.add_parser("stats", help="print store statistics")
|
|
778
|
-
sub_stats.add_argument("db", help="path to sqlite db")
|
|
779
|
-
|
|
780
|
-
args = p.parse_args()
|
|
781
|
-
if args.cmd == "init":
|
|
782
|
-
KGStoreV2(args.db).init_schema()
|
|
783
|
-
print(f"initialized v2 schema in {args.db}")
|
|
784
|
-
return 0
|
|
785
|
-
if args.cmd == "stats":
|
|
786
|
-
print(json.dumps(KGStoreV2(args.db).stats(), indent=2, ensure_ascii=False))
|
|
787
|
-
return 0
|
|
788
|
-
return 2
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if __name__ == "__main__":
|
|
792
|
-
raise SystemExit(_cli())
|