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
package/lattice_brain/memory.py
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"""Memory System — typed, durable memory records on the brain substrate.
|
|
2
|
-
|
|
3
|
-
Decision and Experience records become first-class graph nodes through the
|
|
4
|
-
unified ingestion pipeline (provenance + hooks), instead of markdown dumps
|
|
5
|
-
with swallowed errors. Episodic memory is the conversation store; semantic
|
|
6
|
-
memory is the workspace MEMORY_KINDS records — this module adds the typed
|
|
7
|
-
record kinds the schema always had but never populated.
|
|
8
|
-
|
|
9
|
-
Only REAL events become memories: simulation runs are rejected at this
|
|
10
|
-
boundary (the run record's own mode field is checked — fabricated artifacts
|
|
11
|
-
must never enter the brain as experience).
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from typing import Any, Dict, Optional
|
|
17
|
-
|
|
18
|
-
from .ingestion import IngestionItem
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class BrainMemory:
|
|
22
|
-
"""Writes Decision / Experience records through the ingestion pipeline."""
|
|
23
|
-
|
|
24
|
-
def __init__(self, ingestion_pipeline: Any):
|
|
25
|
-
self._pipeline = ingestion_pipeline
|
|
26
|
-
|
|
27
|
-
def available(self) -> bool:
|
|
28
|
-
return self._pipeline is not None and self._pipeline.available()
|
|
29
|
-
|
|
30
|
-
def record_experience(
|
|
31
|
-
self,
|
|
32
|
-
title: str,
|
|
33
|
-
detail: str = "",
|
|
34
|
-
*,
|
|
35
|
-
run: Optional[Dict[str, Any]] = None,
|
|
36
|
-
user_email: Optional[str] = None,
|
|
37
|
-
workspace_id: Optional[str] = None,
|
|
38
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
39
|
-
) -> Dict[str, Any]:
|
|
40
|
-
"""Persist a completed run/action as an Experience node.
|
|
41
|
-
|
|
42
|
-
``run`` is the persisted run record; simulated runs are refused —
|
|
43
|
-
a simulation is replay scaffolding, not something that happened.
|
|
44
|
-
"""
|
|
45
|
-
if run is not None and run.get("mode", "simulation") == "simulation":
|
|
46
|
-
return {
|
|
47
|
-
"status": "rejected",
|
|
48
|
-
"detail": "simulation runs are not experiences and never enter the brain",
|
|
49
|
-
}
|
|
50
|
-
if not str(title or "").strip():
|
|
51
|
-
raise ValueError("an experience needs a title")
|
|
52
|
-
run_meta = {}
|
|
53
|
-
if run is not None:
|
|
54
|
-
run_meta = {
|
|
55
|
-
"run_id": run.get("id"),
|
|
56
|
-
"agent_id": run.get("agent_id"),
|
|
57
|
-
"run_status": run.get("status"),
|
|
58
|
-
"mode": run.get("mode"),
|
|
59
|
-
"retries": run.get("retries"),
|
|
60
|
-
}
|
|
61
|
-
result = self._pipeline.ingest(
|
|
62
|
-
IngestionItem(
|
|
63
|
-
source_type="experience",
|
|
64
|
-
title=title.strip(),
|
|
65
|
-
text=detail,
|
|
66
|
-
owner=user_email,
|
|
67
|
-
workspace_id=workspace_id,
|
|
68
|
-
metadata={**run_meta, **(metadata or {})},
|
|
69
|
-
),
|
|
70
|
-
user_email=user_email,
|
|
71
|
-
)
|
|
72
|
-
return result.as_dict()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
__all__ = ["BrainMemory"]
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"""Knowledge Graph portability — local export / import / backup / restore.
|
|
2
|
-
|
|
3
|
-
The Knowledge Graph is the user's durable asset, so it must be portable without
|
|
4
|
-
any cloud service. Two complementary mechanisms, both fully local:
|
|
5
|
-
|
|
6
|
-
* **Logical export/import** (JSON): nodes/edges/chunks/sources/provenance with a
|
|
7
|
-
versioned header (schema + projection + embed-dim). Vectors are not in the
|
|
8
|
-
artifact; the importer re-embeds with its own embedder and reports the
|
|
9
|
-
resulting index state under ``result["index"]`` (``degraded: true`` means the
|
|
10
|
-
content landed but recall is lexical-only until a rebuild succeeds). That is
|
|
11
|
-
what makes it portable across machines.
|
|
12
|
-
* **Binary backup/restore** (ZIP): a faithful snapshot of the SQLite DB (incl.
|
|
13
|
-
vector embeddings) plus the blob directory, integrity-checked, for
|
|
14
|
-
same-machine recovery.
|
|
15
|
-
|
|
16
|
-
Split into cohesive submodules in v11.3.0 (no behaviour change): ``constants``
|
|
17
|
-
(formats + gates), ``fsops`` (atomic swaps), ``bundles`` (subgraph shaping),
|
|
18
|
-
``sharing`` and ``backups`` (the two halves of the service), ``service`` (the
|
|
19
|
-
composed class). This module re-exports every name the single file exposed, so
|
|
20
|
-
``lattice_brain.portability.X`` keeps working.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
from __future__ import annotations
|
|
24
|
-
|
|
25
|
-
# The single file had no ``__all__``, so its public surface was "every module
|
|
26
|
-
# global". The redundant-alias form reproduces exactly that surface and marks
|
|
27
|
-
# each name as a deliberate re-export rather than a leftover import.
|
|
28
|
-
#
|
|
29
|
-
# Stubbing note: rebinding one of these *here* changes only this module's name.
|
|
30
|
-
# The submodule that calls it holds its own reference, so a test standing in for
|
|
31
|
-
# ``_stamp`` patches ``lattice_brain.portability.fsops``, for ``_sha256_file``
|
|
32
|
-
# or ``SQLiteToPostgresMigrator`` ``…portability.backups``, and for
|
|
33
|
-
# ``load_recipient_identity`` ``…portability.sharing``.
|
|
34
|
-
from ..archive import KDF_ITERATIONS as KDF_ITERATIONS
|
|
35
|
-
from ..archive import BrainArchivePaths as BrainArchivePaths
|
|
36
|
-
from ..archive import EncryptedBrainArchive as EncryptedBrainArchive
|
|
37
|
-
from ..archive import _derive_key as _derive_key
|
|
38
|
-
from ..gates import FeatureGate as FeatureGate
|
|
39
|
-
from ..quiet import quiet as quiet
|
|
40
|
-
from ..sealed_box import SEALED_BOX_ALGORITHM as SEALED_BOX_ALGORITHM
|
|
41
|
-
from ..sealed_box import public_key_fingerprint as public_key_fingerprint
|
|
42
|
-
from ..sealed_box import seal as seal
|
|
43
|
-
from ..storage import DockerPostgresWizard as DockerPostgresWizard
|
|
44
|
-
from ..storage import PostgresEngine as PostgresEngine
|
|
45
|
-
from ..storage import SQLiteToPostgresMigrator as SQLiteToPostgresMigrator
|
|
46
|
-
from ..utils import utc_now_iso as utc_now_iso
|
|
47
|
-
from .backups import KGPortabilityBackupMixin as KGPortabilityBackupMixin
|
|
48
|
-
from .backups import _sha256_file as _sha256_file
|
|
49
|
-
from .bundles import _canonical_digest as _canonical_digest
|
|
50
|
-
from .bundles import _expand_neighbors as _expand_neighbors
|
|
51
|
-
from .bundles import _node_source_type as _node_source_type
|
|
52
|
-
from .bundles import _redact_node as _redact_node
|
|
53
|
-
from .bundles import _redact_provenance_row as _redact_provenance_row
|
|
54
|
-
from .bundles import _scope_node as _scope_node
|
|
55
|
-
from .bundles import _select_node_ids as _select_node_ids
|
|
56
|
-
from .bundles import _shared_node_summary as _shared_node_summary
|
|
57
|
-
from .bundles import _strip_fields as _strip_fields
|
|
58
|
-
from .constants import _NEIGHBOR_EXCLUDED_TYPES as _NEIGHBOR_EXCLUDED_TYPES
|
|
59
|
-
from .constants import _OPEN_REVIEW_STATUSES as _OPEN_REVIEW_STATUSES
|
|
60
|
-
from .constants import _PAYLOAD_KEYS as _PAYLOAD_KEYS
|
|
61
|
-
from .constants import _REDACTED_FIELDS as _REDACTED_FIELDS
|
|
62
|
-
from .constants import _TRUTHY as _TRUTHY
|
|
63
|
-
from .constants import BACKUP_FORMAT as BACKUP_FORMAT
|
|
64
|
-
from .constants import BRAIN_NETWORK_DISABLED_DETAIL as BRAIN_NETWORK_DISABLED_DETAIL
|
|
65
|
-
from .constants import BRAIN_NETWORK_ENV as BRAIN_NETWORK_ENV
|
|
66
|
-
from .constants import BRAIN_NETWORK_GATE as BRAIN_NETWORK_GATE
|
|
67
|
-
from .constants import ENCRYPTION_MODES as ENCRYPTION_MODES
|
|
68
|
-
from .constants import FORMAT as FORMAT
|
|
69
|
-
from .constants import FORMAT_VERSION as FORMAT_VERSION
|
|
70
|
-
from .constants import SUBGRAPH_ARCHIVE_FORMAT as SUBGRAPH_ARCHIVE_FORMAT
|
|
71
|
-
from .constants import SUBGRAPH_FORMAT as SUBGRAPH_FORMAT
|
|
72
|
-
from .constants import SUBGRAPH_FORMAT_VERSION as SUBGRAPH_FORMAT_VERSION
|
|
73
|
-
from .constants import SUBGRAPH_PROPOSAL_CAP as SUBGRAPH_PROPOSAL_CAP
|
|
74
|
-
from .constants import SUBGRAPH_REVIEW_KIND as SUBGRAPH_REVIEW_KIND
|
|
75
|
-
from .constants import SUBGRAPH_REVIEW_SOURCE as SUBGRAPH_REVIEW_SOURCE
|
|
76
|
-
from .constants import BrainNetworkDisabled as BrainNetworkDisabled
|
|
77
|
-
from .constants import _stamp as _stamp
|
|
78
|
-
from .constants import brain_network_enabled as brain_network_enabled
|
|
79
|
-
from .constants import require_brain_network as require_brain_network
|
|
80
|
-
from .fsops import _checkpoint_sqlite as _checkpoint_sqlite
|
|
81
|
-
from .fsops import _pre_restore_backup_dir as _pre_restore_backup_dir
|
|
82
|
-
from .fsops import _replace_sqlite_atomically as _replace_sqlite_atomically
|
|
83
|
-
from .fsops import _replace_tree_with_backup as _replace_tree_with_backup
|
|
84
|
-
from .fsops import _restore_sibling as _restore_sibling
|
|
85
|
-
from .fsops import _rollback_sqlite_from_backup as _rollback_sqlite_from_backup
|
|
86
|
-
from .fsops import _safe_zip_names as _safe_zip_names
|
|
87
|
-
from .fsops import _sqlite_siblings as _sqlite_siblings
|
|
88
|
-
from .service import KGPortabilityService as KGPortabilityService
|
|
89
|
-
from .sharing import KGPortabilitySharingMixin as KGPortabilitySharingMixin
|
|
90
|
-
from .sharing import load_recipient_identity as load_recipient_identity
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
"""The seam the two portability mixins share.
|
|
2
|
-
|
|
3
|
-
``KGPortabilityService`` is assembled from :class:`KGPortabilitySharingMixin`
|
|
4
|
-
and :class:`KGPortabilityBackupMixin`. Both read constructor state they do not
|
|
5
|
-
own and both call the availability guard the service defines. That contract
|
|
6
|
-
existed as an unwritten convention; this module writes it down.
|
|
7
|
-
|
|
8
|
-
Typing-only, exactly like :mod:`lattice_brain.graph._kg_contract`: at runtime
|
|
9
|
-
the mixins alias it to ``object``, so the MRO and every method resolution stay
|
|
10
|
-
byte-for-byte what the single-file class had. Adding a cross-mixin call without
|
|
11
|
-
declaring it here is a type error.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from typing import Any
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class PortabilityCore:
|
|
21
|
-
"""What either portability mixin may assume about ``self``.
|
|
22
|
-
|
|
23
|
-
Never instantiated and never inherited at runtime — see the module
|
|
24
|
-
docstring. Members are declared, not implemented: the implementation lives
|
|
25
|
-
in ``service.py`` (state and availability) or in whichever mixin owns it.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
# ── State owned by KGPortabilityService.__init__ ─────────────────────────
|
|
29
|
-
_kg: Any
|
|
30
|
-
_data_dir: Path
|
|
31
|
-
_enable: bool
|
|
32
|
-
_exports_dir: Path
|
|
33
|
-
_identity: Any
|
|
34
|
-
_recipient: Any
|
|
35
|
-
_recipient_loaded: bool
|
|
36
|
-
|
|
37
|
-
# ── service.py: the availability guard every write door calls ────────────
|
|
38
|
-
def available(self) -> bool:
|
|
39
|
-
raise NotImplementedError
|
|
40
|
-
|
|
41
|
-
def _require(self) -> None:
|
|
42
|
-
raise NotImplementedError
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
"""Binary backup/restore, the encrypted archive, and the storage status reads.
|
|
2
|
-
|
|
3
|
-
The half of :class:`~lattice_brain.portability.KGPortabilityService` that moves
|
|
4
|
-
*files*: a ZIP snapshot of the database plus blobs, the passphrase-encrypted
|
|
5
|
-
``.latticebrain`` archive, and the status/migration surfaces that sit on top of
|
|
6
|
-
them (a Postgres migration refuses to start without a verified backup).
|
|
7
|
-
|
|
8
|
-
Integrity hashing (``_sha256_file``) and the Postgres migrator are looked up as
|
|
9
|
-
this module's globals, so a test standing in for either patches *this* module.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import json
|
|
15
|
-
import logging
|
|
16
|
-
import tempfile
|
|
17
|
-
import zipfile
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import Any, Dict, Optional
|
|
20
|
-
|
|
21
|
-
from ..archive import BrainArchivePaths, EncryptedBrainArchive
|
|
22
|
-
from ..storage import (
|
|
23
|
-
DockerPostgresWizard,
|
|
24
|
-
PostgresEngine,
|
|
25
|
-
SQLiteToPostgresMigrator,
|
|
26
|
-
)
|
|
27
|
-
from ..utils import sha256_file as _sha256_file
|
|
28
|
-
from ..utils import utc_now_iso
|
|
29
|
-
from ._contract import PortabilityCore as _Core
|
|
30
|
-
from .constants import BACKUP_FORMAT, FORMAT_VERSION, _stamp
|
|
31
|
-
from .fsops import (
|
|
32
|
-
_pre_restore_backup_dir,
|
|
33
|
-
_replace_sqlite_atomically,
|
|
34
|
-
_replace_tree_with_backup,
|
|
35
|
-
_rollback_sqlite_from_backup,
|
|
36
|
-
_safe_zip_names,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class KGPortabilityBackupMixin(_Core):
|
|
41
|
-
"""Backup / restore / storage status. Mixed into ``KGPortabilityService``."""
|
|
42
|
-
|
|
43
|
-
# ── binary backup / restore ──────────────────────────────────────────────
|
|
44
|
-
def backup(self, dest_path=None) -> Dict[str, Any]:
|
|
45
|
-
self._require()
|
|
46
|
-
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
47
|
-
dest = Path(dest_path) if dest_path else self._exports_dir / f"kg-backup-{_stamp()}.zip"
|
|
48
|
-
with tempfile.TemporaryDirectory() as tmp_s:
|
|
49
|
-
tmp = Path(tmp_s)
|
|
50
|
-
db_copy = tmp / "knowledge_graph.sqlite"
|
|
51
|
-
self._kg.backup_database(db_copy)
|
|
52
|
-
manifest = {
|
|
53
|
-
"format": BACKUP_FORMAT,
|
|
54
|
-
"format_version": FORMAT_VERSION,
|
|
55
|
-
**self._kg.schema_versions(),
|
|
56
|
-
"created_at": utc_now_iso(),
|
|
57
|
-
"db_sha256": _sha256_file(db_copy),
|
|
58
|
-
"has_blobs": Path(self._kg.blob_dir).exists(),
|
|
59
|
-
}
|
|
60
|
-
with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
61
|
-
zf.write(db_copy, "knowledge_graph.sqlite")
|
|
62
|
-
zf.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2))
|
|
63
|
-
blob_dir = Path(self._kg.blob_dir)
|
|
64
|
-
if blob_dir.exists():
|
|
65
|
-
for f in blob_dir.rglob("*"):
|
|
66
|
-
if f.is_file():
|
|
67
|
-
zf.write(f, f"blobs/{f.relative_to(blob_dir)}")
|
|
68
|
-
return {"path": str(dest), "bytes": dest.stat().st_size, "manifest": manifest}
|
|
69
|
-
|
|
70
|
-
def restore(
|
|
71
|
-
self,
|
|
72
|
-
archive_path,
|
|
73
|
-
*,
|
|
74
|
-
verify: bool = True,
|
|
75
|
-
dry_run: bool = False,
|
|
76
|
-
confirm: bool = False,
|
|
77
|
-
) -> Dict[str, Any]:
|
|
78
|
-
self._require()
|
|
79
|
-
archive = Path(archive_path)
|
|
80
|
-
if not archive.exists():
|
|
81
|
-
raise FileNotFoundError(f"Backup archive not found: {archive}")
|
|
82
|
-
if not dry_run and not confirm:
|
|
83
|
-
raise ValueError("Explicit confirmation is required before restoring a Knowledge Graph backup.")
|
|
84
|
-
with zipfile.ZipFile(archive) as zf:
|
|
85
|
-
names = zf.namelist()
|
|
86
|
-
_safe_zip_names(names)
|
|
87
|
-
if "knowledge_graph.sqlite" not in names:
|
|
88
|
-
raise ValueError("Archive is missing knowledge_graph.sqlite.")
|
|
89
|
-
manifest = json.loads(zf.read("manifest.json")) if "manifest.json" in names else {}
|
|
90
|
-
with tempfile.TemporaryDirectory() as tmp_s:
|
|
91
|
-
tmp = Path(tmp_s)
|
|
92
|
-
zf.extractall(tmp)
|
|
93
|
-
db_src = tmp / "knowledge_graph.sqlite"
|
|
94
|
-
if verify and manifest.get("db_sha256"):
|
|
95
|
-
if _sha256_file(db_src) != manifest["db_sha256"]:
|
|
96
|
-
raise ValueError("Backup integrity check failed (db sha256 mismatch).")
|
|
97
|
-
if dry_run:
|
|
98
|
-
return {
|
|
99
|
-
"restored": False,
|
|
100
|
-
"dry_run": True,
|
|
101
|
-
"verified": True,
|
|
102
|
-
"manifest": manifest,
|
|
103
|
-
"planned": {
|
|
104
|
-
"database": str(self._kg.db_path),
|
|
105
|
-
"blobs": str(self._kg.blob_dir),
|
|
106
|
-
"archive": str(archive),
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
db_dest = Path(self._kg.db_path)
|
|
110
|
-
blob_dest = Path(self._kg.blob_dir)
|
|
111
|
-
backup_dir = _pre_restore_backup_dir(db_dest)
|
|
112
|
-
try:
|
|
113
|
-
_replace_sqlite_atomically(db_src, db_dest, backup_dir)
|
|
114
|
-
blob_src = tmp / "blobs"
|
|
115
|
-
_replace_tree_with_backup(blob_src if blob_src.exists() else None, blob_dest, backup_dir)
|
|
116
|
-
except Exception:
|
|
117
|
-
# The rollback is best-effort recovery; an I/O slip inside
|
|
118
|
-
# it must never mask the restore failure being reported.
|
|
119
|
-
try:
|
|
120
|
-
_rollback_sqlite_from_backup(db_dest, backup_dir)
|
|
121
|
-
except OSError as rollback_exc:
|
|
122
|
-
logging.warning(
|
|
123
|
-
"pre-restore rollback incomplete (backup kept at %s): %s",
|
|
124
|
-
backup_dir, rollback_exc,
|
|
125
|
-
)
|
|
126
|
-
raise
|
|
127
|
-
stats = self._kg.stats()
|
|
128
|
-
return {
|
|
129
|
-
"restored": True,
|
|
130
|
-
"manifest": manifest,
|
|
131
|
-
"pre_restore_backup": str(backup_dir),
|
|
132
|
-
"nodes": sum(stats.get("nodes", {}).values()),
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
def verify_backup(self, archive_path) -> Dict[str, Any]:
|
|
136
|
-
archive = Path(archive_path)
|
|
137
|
-
if not archive.exists():
|
|
138
|
-
return {"ok": False, "path": str(archive), "errors": [f"Backup archive not found: {archive}"]}
|
|
139
|
-
try:
|
|
140
|
-
with zipfile.ZipFile(archive) as zf:
|
|
141
|
-
names = zf.namelist()
|
|
142
|
-
_safe_zip_names(names)
|
|
143
|
-
if "knowledge_graph.sqlite" not in names:
|
|
144
|
-
raise ValueError("Archive is missing knowledge_graph.sqlite.")
|
|
145
|
-
manifest = json.loads(zf.read("manifest.json")) if "manifest.json" in names else {}
|
|
146
|
-
with tempfile.TemporaryDirectory() as tmp_s:
|
|
147
|
-
tmp = Path(tmp_s)
|
|
148
|
-
zf.extract("knowledge_graph.sqlite", tmp)
|
|
149
|
-
db_src = tmp / "knowledge_graph.sqlite"
|
|
150
|
-
if manifest.get("db_sha256") and _sha256_file(db_src) != manifest["db_sha256"]:
|
|
151
|
-
raise ValueError("Backup integrity check failed (db sha256 mismatch).")
|
|
152
|
-
return {"ok": True, "path": str(archive), "manifest": manifest, "errors": []}
|
|
153
|
-
except (ValueError, zipfile.BadZipFile, OSError, json.JSONDecodeError) as exc:
|
|
154
|
-
return {"ok": False, "path": str(archive), "errors": [str(exc)]}
|
|
155
|
-
|
|
156
|
-
# ── encrypted .latticebrain archive ───────────────────────────────────
|
|
157
|
-
def encrypted_archive(self, dest_path=None, *, passphrase: str) -> Dict[str, Any]:
|
|
158
|
-
self._require()
|
|
159
|
-
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
160
|
-
dest = Path(dest_path) if dest_path else self._exports_dir / f"brain-{_stamp()}.latticebrain"
|
|
161
|
-
metadata = {
|
|
162
|
-
"storage": self.storage_status().get("active", {}),
|
|
163
|
-
"snapshot": self.snapshot_metadata(),
|
|
164
|
-
"device_identity": self._identity.describe() if self._identity is not None else {},
|
|
165
|
-
"provenance": {"exported_at": utc_now_iso(), "source": "kg-portability"},
|
|
166
|
-
}
|
|
167
|
-
archive = EncryptedBrainArchive(
|
|
168
|
-
BrainArchivePaths(
|
|
169
|
-
db_path=Path(self._kg.db_path),
|
|
170
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
171
|
-
data_dir=self._data_dir,
|
|
172
|
-
metadata=metadata,
|
|
173
|
-
)
|
|
174
|
-
)
|
|
175
|
-
return archive.create(dest, passphrase=passphrase)
|
|
176
|
-
|
|
177
|
-
def inspect_encrypted_archive(self, archive_path, *, passphrase: Optional[str] = None) -> Dict[str, Any]:
|
|
178
|
-
archive = EncryptedBrainArchive(
|
|
179
|
-
BrainArchivePaths(
|
|
180
|
-
db_path=Path(self._kg.db_path),
|
|
181
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
182
|
-
data_dir=self._data_dir,
|
|
183
|
-
)
|
|
184
|
-
)
|
|
185
|
-
return archive.inspect(Path(archive_path), passphrase=passphrase)
|
|
186
|
-
|
|
187
|
-
def verify_encrypted_archive(self, archive_path, *, passphrase: str) -> Dict[str, Any]:
|
|
188
|
-
archive = EncryptedBrainArchive(
|
|
189
|
-
BrainArchivePaths(
|
|
190
|
-
db_path=Path(self._kg.db_path),
|
|
191
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
192
|
-
data_dir=self._data_dir,
|
|
193
|
-
)
|
|
194
|
-
)
|
|
195
|
-
return archive.verify(Path(archive_path), passphrase=passphrase)
|
|
196
|
-
|
|
197
|
-
def restore_encrypted_archive(
|
|
198
|
-
self,
|
|
199
|
-
archive_path,
|
|
200
|
-
*,
|
|
201
|
-
passphrase: str,
|
|
202
|
-
dry_run: bool = False,
|
|
203
|
-
confirm: bool = False,
|
|
204
|
-
) -> Dict[str, Any]:
|
|
205
|
-
self._require()
|
|
206
|
-
archive = EncryptedBrainArchive(
|
|
207
|
-
BrainArchivePaths(
|
|
208
|
-
db_path=Path(self._kg.db_path),
|
|
209
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
210
|
-
data_dir=self._data_dir,
|
|
211
|
-
)
|
|
212
|
-
)
|
|
213
|
-
return archive.restore(
|
|
214
|
-
Path(archive_path),
|
|
215
|
-
passphrase=passphrase,
|
|
216
|
-
target=BrainArchivePaths(
|
|
217
|
-
db_path=Path(self._kg.db_path),
|
|
218
|
-
blob_dir=Path(self._kg.blob_dir),
|
|
219
|
-
data_dir=self._data_dir,
|
|
220
|
-
),
|
|
221
|
-
dry_run=dry_run,
|
|
222
|
-
confirm=confirm,
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
def import_encrypted_archive(
|
|
226
|
-
self,
|
|
227
|
-
archive_path,
|
|
228
|
-
*,
|
|
229
|
-
passphrase: str,
|
|
230
|
-
dry_run: bool = False,
|
|
231
|
-
confirm: bool = False,
|
|
232
|
-
) -> Dict[str, Any]:
|
|
233
|
-
result = self.restore_encrypted_archive(
|
|
234
|
-
archive_path,
|
|
235
|
-
passphrase=passphrase,
|
|
236
|
-
dry_run=dry_run,
|
|
237
|
-
confirm=confirm,
|
|
238
|
-
)
|
|
239
|
-
result["operation"] = "import"
|
|
240
|
-
return result
|
|
241
|
-
|
|
242
|
-
# ── status surface ───────────────────────────────────────────────────────
|
|
243
|
-
def snapshot_metadata(self) -> Dict[str, Any]:
|
|
244
|
-
if not self.available():
|
|
245
|
-
return {"available": False}
|
|
246
|
-
return {
|
|
247
|
-
"available": True,
|
|
248
|
-
**self._kg.schema_versions(),
|
|
249
|
-
"stats": self._kg.stats(),
|
|
250
|
-
"provenance": self._kg.provenance_stats(),
|
|
251
|
-
"storage": (
|
|
252
|
-
self._kg.storage_engine.capabilities().as_dict()
|
|
253
|
-
if getattr(self._kg, "storage_engine", None) is not None
|
|
254
|
-
else {"engine": "sqlite", "available": True}
|
|
255
|
-
),
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
def storage_status(self) -> Dict[str, Any]:
|
|
259
|
-
if not self.available():
|
|
260
|
-
return {"available": False}
|
|
261
|
-
return {
|
|
262
|
-
"available": True,
|
|
263
|
-
"active": (
|
|
264
|
-
self._kg.storage_engine.capabilities().as_dict()
|
|
265
|
-
if getattr(self._kg, "storage_engine", None) is not None
|
|
266
|
-
else {"engine": "sqlite", "available": True}
|
|
267
|
-
),
|
|
268
|
-
"postgres": PostgresEngine("", schema="lattice_brain").capabilities().as_dict(),
|
|
269
|
-
"backup_health": self.backup_health(),
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
def backup_health(self) -> Dict[str, Any]:
|
|
273
|
-
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
274
|
-
backups = sorted(
|
|
275
|
-
[
|
|
276
|
-
p for p in self._exports_dir.glob("*")
|
|
277
|
-
if p.is_file() and (p.suffix == ".zip" or p.suffix == ".latticebrain")
|
|
278
|
-
],
|
|
279
|
-
key=lambda p: p.stat().st_mtime,
|
|
280
|
-
reverse=True,
|
|
281
|
-
)
|
|
282
|
-
latest = backups[0] if backups else None
|
|
283
|
-
return {
|
|
284
|
-
"available": True,
|
|
285
|
-
"directory": str(self._exports_dir),
|
|
286
|
-
"count": len(backups),
|
|
287
|
-
"latest": str(latest) if latest else None,
|
|
288
|
-
"latest_bytes": latest.stat().st_size if latest else 0,
|
|
289
|
-
"encrypted_archives": sum(1 for p in backups if p.suffix == ".latticebrain"),
|
|
290
|
-
"zip_backups": sum(1 for p in backups if p.suffix == ".zip"),
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
def postgres_docker_setup(
|
|
294
|
-
self,
|
|
295
|
-
*,
|
|
296
|
-
consent: bool,
|
|
297
|
-
dry_run: bool = False,
|
|
298
|
-
port: int = 5432,
|
|
299
|
-
) -> Dict[str, Any]:
|
|
300
|
-
wizard = DockerPostgresWizard(self._data_dir / "postgres", port=port)
|
|
301
|
-
return wizard.start(consent=consent, dry_run=dry_run)
|
|
302
|
-
|
|
303
|
-
def migrate_sqlite_to_postgres(
|
|
304
|
-
self,
|
|
305
|
-
*,
|
|
306
|
-
dsn: str,
|
|
307
|
-
schema: str = "lattice_brain",
|
|
308
|
-
dry_run: bool = True,
|
|
309
|
-
) -> Dict[str, Any]:
|
|
310
|
-
self._require()
|
|
311
|
-
if not dsn:
|
|
312
|
-
raise ValueError("Postgres DSN is required for SQLite to Postgres migration.")
|
|
313
|
-
migrator = SQLiteToPostgresMigrator(
|
|
314
|
-
Path(self._kg.db_path),
|
|
315
|
-
PostgresEngine(dsn, schema=schema),
|
|
316
|
-
)
|
|
317
|
-
if dry_run:
|
|
318
|
-
return migrator.migrate(dry_run=True)
|
|
319
|
-
backup = self.backup()
|
|
320
|
-
verification = self.verify_backup(backup["path"])
|
|
321
|
-
if not verification.get("ok"):
|
|
322
|
-
raise RuntimeError(
|
|
323
|
-
"Pre-migration backup verification failed; Postgres migration was not started: "
|
|
324
|
-
+ "; ".join(verification.get("errors") or [])
|
|
325
|
-
)
|
|
326
|
-
result = migrator.migrate(dry_run=False)
|
|
327
|
-
result["pre_migration_backup"] = {
|
|
328
|
-
"path": backup["path"],
|
|
329
|
-
"verified": True,
|
|
330
|
-
"manifest": backup.get("manifest"),
|
|
331
|
-
}
|
|
332
|
-
return result
|
|
333
|
-
|
|
334
|
-
def recent_ingestions(self, *, limit: int = 50, source_type: Optional[str] = None) -> Dict[str, Any]:
|
|
335
|
-
"""Recent provenance records (newest first) for the ingestion-sources UI."""
|
|
336
|
-
if not self.available():
|
|
337
|
-
return {"items": [], "count": 0}
|
|
338
|
-
return self._kg.list_provenance(limit=limit, source_type=source_type)
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
"""Pure shaping of a shared subgraph bundle: select, expand, redact, digest.
|
|
2
|
-
|
|
3
|
-
None of these touch a store or a file. They decide *what* travels — which
|
|
4
|
-
nodes the selection names, which neighbours a one-hop expansion may admit, and
|
|
5
|
-
which sender-identifying fields are stripped on the way out — and they pin the
|
|
6
|
-
result with the digest the signature covers.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import hashlib
|
|
12
|
-
import json
|
|
13
|
-
from typing import Any, Dict, List, Optional, Set
|
|
14
|
-
|
|
15
|
-
from .constants import _NEIGHBOR_EXCLUDED_TYPES, _REDACTED_FIELDS
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _canonical_digest(payload: Dict[str, Any]) -> str:
|
|
19
|
-
"""sha256 over the canonical JSON of a bundle payload.
|
|
20
|
-
|
|
21
|
-
Pinned in the signed header so the Ed25519 signature covers the contents,
|
|
22
|
-
not just the manifest describing them.
|
|
23
|
-
"""
|
|
24
|
-
canonical = json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8")
|
|
25
|
-
return hashlib.sha256(canonical).hexdigest()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _node_source_type(node: Dict[str, Any]) -> str:
|
|
29
|
-
try:
|
|
30
|
-
metadata = json.loads(node.get("metadata_json") or "{}")
|
|
31
|
-
except (TypeError, ValueError):
|
|
32
|
-
return ""
|
|
33
|
-
if not isinstance(metadata, dict):
|
|
34
|
-
return ""
|
|
35
|
-
return str(metadata.get("source_type") or "").strip().lower()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _select_node_ids(nodes: List[Dict[str, Any]], selection: Dict[str, Any]) -> Set[str]:
|
|
39
|
-
wanted_ids = set(selection["node_ids"])
|
|
40
|
-
wanted_types = set(selection["node_types"])
|
|
41
|
-
wanted_sources = set(selection["source_types"])
|
|
42
|
-
keep: Set[str] = set()
|
|
43
|
-
for node in nodes:
|
|
44
|
-
node_id = str(node.get("id"))
|
|
45
|
-
if node_id in wanted_ids:
|
|
46
|
-
keep.add(node_id)
|
|
47
|
-
continue
|
|
48
|
-
if wanted_types and str(node.get("type") or "").strip().lower() in wanted_types:
|
|
49
|
-
keep.add(node_id)
|
|
50
|
-
continue
|
|
51
|
-
if wanted_sources and _node_source_type(node) in wanted_sources:
|
|
52
|
-
keep.add(node_id)
|
|
53
|
-
return keep
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _expand_neighbors(
|
|
57
|
-
keep: Set[str], edges: List[Dict[str, Any]], known: Dict[str, str]
|
|
58
|
-
) -> Set[str]:
|
|
59
|
-
"""One hop out from the selection, in both directions, within the scope.
|
|
60
|
-
|
|
61
|
-
``Person`` and ``Source`` neighbours are never pulled in implicitly: a
|
|
62
|
-
Person node's label *is* an email address and a Source node's summary *is*
|
|
63
|
-
a local file path, so "share this decision and what it touches" would
|
|
64
|
-
quietly ship the sender's identity and directory layout. Naming one of
|
|
65
|
-
those node ids explicitly still exports it — that is a decision, not a
|
|
66
|
-
side effect.
|
|
67
|
-
"""
|
|
68
|
-
expanded = set(keep)
|
|
69
|
-
|
|
70
|
-
def _admit(candidate: str) -> None:
|
|
71
|
-
if candidate in known and known[candidate] not in _NEIGHBOR_EXCLUDED_TYPES:
|
|
72
|
-
expanded.add(candidate)
|
|
73
|
-
|
|
74
|
-
for edge in edges:
|
|
75
|
-
source = str(edge.get("from_node"))
|
|
76
|
-
target = str(edge.get("to_node"))
|
|
77
|
-
if source in keep:
|
|
78
|
-
_admit(target)
|
|
79
|
-
if target in keep:
|
|
80
|
-
_admit(source)
|
|
81
|
-
return expanded
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _strip_fields(raw_json: Optional[str]) -> Optional[str]:
|
|
85
|
-
if not raw_json:
|
|
86
|
-
return raw_json
|
|
87
|
-
try:
|
|
88
|
-
parsed = json.loads(raw_json)
|
|
89
|
-
except (TypeError, ValueError):
|
|
90
|
-
return raw_json
|
|
91
|
-
if not isinstance(parsed, dict):
|
|
92
|
-
return raw_json
|
|
93
|
-
cleaned = {k: v for k, v in parsed.items() if k not in _REDACTED_FIELDS}
|
|
94
|
-
return json.dumps(cleaned, ensure_ascii=False, sort_keys=True)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def _redact_node(node: Dict[str, Any]) -> Dict[str, Any]:
|
|
98
|
-
redacted = dict(node)
|
|
99
|
-
redacted["metadata_json"] = _strip_fields(node.get("metadata_json"))
|
|
100
|
-
redacted["raw_json"] = _strip_fields(node.get("raw_json"))
|
|
101
|
-
return redacted
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def _redact_provenance_row(row: Dict[str, Any]) -> Dict[str, Any]:
|
|
105
|
-
redacted = dict(row)
|
|
106
|
-
redacted["owner"] = None
|
|
107
|
-
redacted["source_uri"] = None
|
|
108
|
-
redacted["permissions_json"] = "{}"
|
|
109
|
-
redacted["metadata_json"] = _strip_fields(row.get("metadata_json"))
|
|
110
|
-
return redacted
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _scope_node(node: Dict[str, Any], workspace_id: str) -> Dict[str, Any]:
|
|
114
|
-
"""Stamp the accepting workspace onto an incoming node.
|
|
115
|
-
|
|
116
|
-
Received knowledge belongs to the workspace that accepted it. Without this
|
|
117
|
-
the projection would file it as legacy-global — machine-shared — which is a
|
|
118
|
-
wider scope than the reviewer chose.
|
|
119
|
-
"""
|
|
120
|
-
scoped = dict(node)
|
|
121
|
-
try:
|
|
122
|
-
metadata = json.loads(scoped.get("metadata_json") or "{}")
|
|
123
|
-
except (TypeError, ValueError):
|
|
124
|
-
metadata = {}
|
|
125
|
-
if not isinstance(metadata, dict):
|
|
126
|
-
metadata = {}
|
|
127
|
-
metadata["workspace_id"] = workspace_id
|
|
128
|
-
scoped["metadata_json"] = json.dumps(metadata, ensure_ascii=False, sort_keys=True)
|
|
129
|
-
return scoped
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _shared_node_summary(node: Dict[str, Any], verdict: Dict[str, Any]) -> str:
|
|
133
|
-
body = str(node.get("summary") or "").strip()
|
|
134
|
-
origin = verdict.get("origin") or "unknown device"
|
|
135
|
-
prefix = f"[{origin}] "
|
|
136
|
-
return (prefix + body)[:500] if body else prefix.strip()
|