ltcai 11.5.2 → 11.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (443) hide show
  1. package/README.md +93 -148
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +119 -0
  4. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  5. package/docs/DEVELOPMENT.md +8 -6
  6. package/docs/ENTERPRISE.md +2 -1
  7. package/docs/MULTI_AGENT_RUNTIME.md +12 -5
  8. package/docs/ONBOARDING.md +1 -1
  9. package/docs/OPERATIONS.md +6 -3
  10. package/docs/REALTIME_COLLABORATION.md +1 -1
  11. package/docs/TRUST_MODEL.md +1 -1
  12. package/docs/WHY_LATTICE.md +1 -1
  13. package/docs/WORKFLOW_DESIGNER.md +3 -2
  14. package/docs/kg-schema.md +1 -1
  15. package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
  16. package/lattice_brain/__init__.py +42 -79
  17. package/lattice_brain/graph/__init__.py +9 -24
  18. package/lattice_brain/graph/_kg_common/__init__.py +13 -24
  19. package/lattice_brain/ingestion/__init__.py +19 -58
  20. package/lattice_brain/ingestion/pipeline.py +20 -398
  21. package/lattice_brain/multimodal/__init__.py +7 -18
  22. package/lattice_brain/multimodal/images.py +6 -264
  23. package/lattice_brain/multimodal/video.py +8 -247
  24. package/lattice_brain/runtime/__init__.py +8 -79
  25. package/lattice_brain/runtime/hooks.py +22 -584
  26. package/latticeai/__init__.py +1 -1
  27. package/latticeai/api/agent_worker_seam.py +7 -80
  28. package/latticeai/api/health.py +6 -20
  29. package/latticeai/api/local_files.py +16 -613
  30. package/latticeai/api/models.py +12 -87
  31. package/latticeai/api/search.py +17 -255
  32. package/latticeai/api/tools.py +69 -750
  33. package/latticeai/api/voice_capture.py +8 -70
  34. package/latticeai/api/worker_compute.py +842 -0
  35. package/latticeai/api/worker_seams.py +216 -0
  36. package/latticeai/app_factory.py +23 -232
  37. package/latticeai/cli/entrypoint.py +36 -249
  38. package/latticeai/core/agent_permission.py +16 -91
  39. package/latticeai/core/messages.py +89 -531
  40. package/latticeai/runtime/access_runtime.py +0 -14
  41. package/latticeai/runtime/bootstrap.py +10 -21
  42. package/latticeai/runtime/brain_runtime.py +35 -43
  43. package/latticeai/runtime/build_phases/__init__.py +21 -37
  44. package/latticeai/runtime/build_phases/features.py +99 -389
  45. package/latticeai/runtime/build_phases/foundation.py +82 -392
  46. package/latticeai/runtime/build_phases/web.py +91 -408
  47. package/latticeai/runtime/build_phases/worker_profile.py +244 -0
  48. package/latticeai/runtime/lifespan_runtime.py +7 -16
  49. package/latticeai/runtime/platform_services_runtime.py +1 -15
  50. package/latticeai/runtime/runtime_context.py +13 -130
  51. package/latticeai/runtime/security_runtime.py +10 -103
  52. package/latticeai/services/architecture_readiness.py +82 -43
  53. package/latticeai/services/model_runtime/__init__.py +2 -11
  54. package/latticeai/services/model_runtime/service.py +4 -28
  55. package/latticeai/services/p_reinforce.py +10 -261
  56. package/latticeai/services/product_readiness.py +31 -38
  57. package/latticeai/services/search_service.py +37 -795
  58. package/latticeai/services/tool_dispatch.py +30 -386
  59. package/latticeai/services/voice_capture.py +13 -107
  60. package/latticeai/tools/__init__.py +22 -49
  61. package/latticeai/tools/commands.py +0 -163
  62. package/latticeai/tools/computer.py +0 -39
  63. package/latticeai/tools/documents.py +1 -134
  64. package/latticeai/tools/filesystem.py +1 -247
  65. package/latticeai/tools/knowledge.py +1 -52
  66. package/latticeai/tools/local_files.py +0 -20
  67. package/latticeai/worker_app.py +75 -0
  68. package/package.json +2 -3
  69. package/requirements.txt +0 -5
  70. package/scripts/agent_eval.py +16 -28
  71. package/scripts/brain_quality_eval.py +20 -183
  72. package/scripts/bump_version.py +5 -5
  73. package/scripts/check_current_release_docs.mjs +7 -4
  74. package/scripts/check_openapi_drift.mjs +10 -0
  75. package/scripts/check_server_i18n.mjs +2 -18
  76. package/scripts/compose_openapi.py +377 -0
  77. package/scripts/export_openapi.py +32 -4
  78. package/scripts/gen_messages_catalog_fixture.py +302 -0
  79. package/scripts/gen_openapi_fragments.py +365 -0
  80. package/scripts/gen_redact_fixture.py +196 -0
  81. package/scripts/gen_worker_allowlist_fixture.py +115 -0
  82. package/scripts/generate_agent_parity_fixtures.py +33 -14
  83. package/scripts/openapi_route_families.json +2161 -0
  84. package/scripts/release_screen_claims.json +54 -0
  85. package/scripts/run_integration_tests.mjs +134 -29
  86. package/scripts/run_sidecar_e2e.mjs +104 -11
  87. package/scripts/wheel_smoke.py +32 -22
  88. package/src-tauri/Cargo.lock +375 -9
  89. package/src-tauri/Cargo.toml +1 -1
  90. package/src-tauri/src/backend.rs +151 -45
  91. package/src-tauri/src/main.rs +18 -18
  92. package/src-tauri/src/topology.rs +58 -88
  93. package/src-tauri/tauri.conf.json +1 -2
  94. package/static/app/asset-manifest.json +41 -41
  95. package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
  96. package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
  97. package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
  98. package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
  99. package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
  100. package/static/app/assets/Capture-BsTokYkk.js +1 -0
  101. package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
  102. package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
  103. package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
  104. package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
  105. package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
  106. package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
  107. package/static/app/assets/System-CMHSO9qM.js +1 -0
  108. package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
  109. package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
  110. package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
  111. package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
  112. package/static/app/assets/circle-check-DruOxB-4.js +1 -0
  113. package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
  114. package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
  115. package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
  116. package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
  117. package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
  118. package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
  119. package/static/app/assets/index-D9x-kSNy.css +2 -0
  120. package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
  121. package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
  122. package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
  124. package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
  125. package/static/app/assets/search-CT9aho2j.js +1 -0
  126. package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
  127. package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
  128. package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
  130. package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
  131. package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
  132. package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
  133. package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
  134. package/static/app/index.html +4 -4
  135. package/static/sw.js +1 -1
  136. package/lattice_brain/archive.py +0 -522
  137. package/lattice_brain/context.py +0 -325
  138. package/lattice_brain/conversations.py +0 -382
  139. package/lattice_brain/core.py +0 -82
  140. package/lattice_brain/graph/_kg_contract.py +0 -249
  141. package/lattice_brain/graph/curator.py +0 -676
  142. package/lattice_brain/graph/discovery.py +0 -595
  143. package/lattice_brain/graph/discovery_index/__init__.py +0 -35
  144. package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
  145. package/lattice_brain/graph/discovery_index/extract.py +0 -137
  146. package/lattice_brain/graph/discovery_index/scan.py +0 -411
  147. package/lattice_brain/graph/discovery_index/upsert.py +0 -495
  148. package/lattice_brain/graph/documents.py +0 -380
  149. package/lattice_brain/graph/fusion.py +0 -395
  150. package/lattice_brain/graph/identity.py +0 -175
  151. package/lattice_brain/graph/image_vectors.py +0 -230
  152. package/lattice_brain/graph/ingest.py +0 -829
  153. package/lattice_brain/graph/network.py +0 -205
  154. package/lattice_brain/graph/proactive.py +0 -724
  155. package/lattice_brain/graph/projection/__init__.py +0 -42
  156. package/lattice_brain/graph/projection/curation.py +0 -500
  157. package/lattice_brain/graph/projection/v2_schema.py +0 -518
  158. package/lattice_brain/graph/provenance.py +0 -524
  159. package/lattice_brain/graph/rerank.py +0 -163
  160. package/lattice_brain/graph/retrieval/__init__.py +0 -54
  161. package/lattice_brain/graph/retrieval/context.py +0 -197
  162. package/lattice_brain/graph/retrieval/graph_view.py +0 -319
  163. package/lattice_brain/graph/retrieval/hybrid.py +0 -488
  164. package/lattice_brain/graph/retrieval/maintenance.py +0 -121
  165. package/lattice_brain/graph/retrieval/signals.py +0 -95
  166. package/lattice_brain/graph/retrieval_docgen.py +0 -253
  167. package/lattice_brain/graph/retrieval_policy.py +0 -180
  168. package/lattice_brain/graph/retrieval_reads.py +0 -769
  169. package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
  170. package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
  171. package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
  172. package/lattice_brain/graph/retrieval_vector/search.py +0 -560
  173. package/lattice_brain/graph/retrieval_vector/status.py +0 -374
  174. package/lattice_brain/graph/schema.py +0 -792
  175. package/lattice_brain/graph/store.py +0 -268
  176. package/lattice_brain/graph/vector_index/__init__.py +0 -85
  177. package/lattice_brain/graph/vector_index/base.py +0 -167
  178. package/lattice_brain/graph/vector_index/brute_force.py +0 -110
  179. package/lattice_brain/graph/vector_index/hnsw.py +0 -290
  180. package/lattice_brain/graph/vector_index/jobs.py +0 -287
  181. package/lattice_brain/graph/vector_index/quantized.py +0 -148
  182. package/lattice_brain/graph/vector_index/selector.py +0 -161
  183. package/lattice_brain/graph/write_master.py +0 -308
  184. package/lattice_brain/ingestion/_contract.py +0 -90
  185. package/lattice_brain/ingestion/folder_scan.py +0 -57
  186. package/lattice_brain/ingestion/folders.py +0 -258
  187. package/lattice_brain/ingestion/jobs_api.py +0 -107
  188. package/lattice_brain/ingestion/routing.py +0 -295
  189. package/lattice_brain/ingestion_jobs.py +0 -380
  190. package/lattice_brain/memory.py +0 -75
  191. package/lattice_brain/portability/__init__.py +0 -90
  192. package/lattice_brain/portability/_contract.py +0 -42
  193. package/lattice_brain/portability/backups.py +0 -338
  194. package/lattice_brain/portability/bundles.py +0 -136
  195. package/lattice_brain/portability/constants.py +0 -93
  196. package/lattice_brain/portability/fsops.py +0 -138
  197. package/lattice_brain/portability/service.py +0 -41
  198. package/lattice_brain/portability/sharing.py +0 -710
  199. package/lattice_brain/quality.py +0 -543
  200. package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
  201. package/lattice_brain/runtime/agent_runtime.py +0 -859
  202. package/lattice_brain/runtime/contracts.py +0 -460
  203. package/lattice_brain/runtime/multi_agent.py +0 -942
  204. package/lattice_brain/runtime/statuses.py +0 -10
  205. package/lattice_brain/sealed_box.py +0 -240
  206. package/lattice_brain/self_model.py +0 -675
  207. package/lattice_brain/sensitivity.py +0 -94
  208. package/lattice_brain/storage/__init__.py +0 -22
  209. package/lattice_brain/storage/base.py +0 -100
  210. package/lattice_brain/storage/docker.py +0 -105
  211. package/lattice_brain/storage/factory.py +0 -31
  212. package/lattice_brain/storage/migration.py +0 -191
  213. package/lattice_brain/storage/postgres.py +0 -123
  214. package/lattice_brain/storage/sqlite.py +0 -143
  215. package/lattice_brain/synthesis.py +0 -824
  216. package/lattice_brain/workflow.py +0 -497
  217. package/latticeai/api/admin.py +0 -471
  218. package/latticeai/api/agent_registry.py +0 -105
  219. package/latticeai/api/agents.py +0 -228
  220. package/latticeai/api/auth.py +0 -383
  221. package/latticeai/api/automation_intelligence.py +0 -401
  222. package/latticeai/api/brain_intelligence.py +0 -199
  223. package/latticeai/api/browser.py +0 -493
  224. package/latticeai/api/change_proposals.py +0 -89
  225. package/latticeai/api/chat.py +0 -572
  226. package/latticeai/api/chat_agent_http.py +0 -892
  227. package/latticeai/api/chat_contracts.py +0 -73
  228. package/latticeai/api/chat_documents.py +0 -276
  229. package/latticeai/api/chat_helpers.py +0 -460
  230. package/latticeai/api/chat_history.py +0 -101
  231. package/latticeai/api/chat_hybrid.py +0 -113
  232. package/latticeai/api/chat_intents.py +0 -646
  233. package/latticeai/api/chat_stream.py +0 -216
  234. package/latticeai/api/chronicle.py +0 -63
  235. package/latticeai/api/command_center.py +0 -51
  236. package/latticeai/api/computer_use.py +0 -474
  237. package/latticeai/api/evidence_actions.py +0 -48
  238. package/latticeai/api/features.py +0 -70
  239. package/latticeai/api/funnel_metrics.py +0 -31
  240. package/latticeai/api/garden.py +0 -34
  241. package/latticeai/api/hooks.py +0 -165
  242. package/latticeai/api/index_jobs.py +0 -145
  243. package/latticeai/api/invitations.py +0 -100
  244. package/latticeai/api/knowledge_graph.py +0 -536
  245. package/latticeai/api/marketplace.py +0 -105
  246. package/latticeai/api/mcp.py +0 -482
  247. package/latticeai/api/memory.py +0 -270
  248. package/latticeai/api/network.py +0 -81
  249. package/latticeai/api/network_boundary.py +0 -225
  250. package/latticeai/api/permission_mode.py +0 -61
  251. package/latticeai/api/permissions.py +0 -436
  252. package/latticeai/api/plugins.py +0 -126
  253. package/latticeai/api/portability.py +0 -391
  254. package/latticeai/api/project_sessions.py +0 -114
  255. package/latticeai/api/realtime.py +0 -118
  256. package/latticeai/api/review_queue.py +0 -364
  257. package/latticeai/api/security_dashboard.py +0 -604
  258. package/latticeai/api/setup.py +0 -319
  259. package/latticeai/api/static_routes.py +0 -354
  260. package/latticeai/api/ui_redirects.py +0 -26
  261. package/latticeai/api/workflow_designer.py +0 -394
  262. package/latticeai/api/workspace.py +0 -856
  263. package/latticeai/api/workspace_scope.py +0 -125
  264. package/latticeai/core/agent/__init__.py +0 -93
  265. package/latticeai/core/agent/_contract.py +0 -79
  266. package/latticeai/core/agent/context.py +0 -57
  267. package/latticeai/core/agent/deps.py +0 -125
  268. package/latticeai/core/agent/execution.py +0 -622
  269. package/latticeai/core/agent/planning.py +0 -145
  270. package/latticeai/core/agent/recovery.py +0 -157
  271. package/latticeai/core/agent/runtime.py +0 -210
  272. package/latticeai/core/agent/verification.py +0 -231
  273. package/latticeai/core/agent_eval.py +0 -739
  274. package/latticeai/core/agent_helpers.py +0 -493
  275. package/latticeai/core/agent_profiles.py +0 -110
  276. package/latticeai/core/agent_prompts.py +0 -171
  277. package/latticeai/core/agent_registry.py +0 -232
  278. package/latticeai/core/agent_state.py +0 -41
  279. package/latticeai/core/agent_trace.py +0 -104
  280. package/latticeai/core/artifact_ledger.py +0 -109
  281. package/latticeai/core/audit.py +0 -260
  282. package/latticeai/core/builtin_hooks.py +0 -105
  283. package/latticeai/core/context_builder.py +0 -394
  284. package/latticeai/core/document_generator.py +0 -103
  285. package/latticeai/core/enterprise.py +0 -154
  286. package/latticeai/core/enterprise_admin.py +0 -158
  287. package/latticeai/core/file_generation/__init__.py +0 -115
  288. package/latticeai/core/file_generation/bundles.py +0 -76
  289. package/latticeai/core/file_generation/extraction.py +0 -154
  290. package/latticeai/core/file_generation/inference.py +0 -235
  291. package/latticeai/core/file_generation/orchestration.py +0 -152
  292. package/latticeai/core/file_generation/prompting.py +0 -117
  293. package/latticeai/core/file_generation/repair.py +0 -114
  294. package/latticeai/core/file_generation/sanitize.py +0 -61
  295. package/latticeai/core/file_generation/validation.py +0 -201
  296. package/latticeai/core/invitations.py +0 -132
  297. package/latticeai/core/legacy_compatibility.py +0 -243
  298. package/latticeai/core/logging_safety.py +0 -46
  299. package/latticeai/core/marketplace.py +0 -293
  300. package/latticeai/core/mcp_catalog.py +0 -452
  301. package/latticeai/core/mcp_registry.py +0 -506
  302. package/latticeai/core/network_boundary.py +0 -168
  303. package/latticeai/core/oidc.py +0 -208
  304. package/latticeai/core/plugins.py +0 -432
  305. package/latticeai/core/product_hardening.py +0 -218
  306. package/latticeai/core/project_sessions.py +0 -337
  307. package/latticeai/core/realtime.py +0 -238
  308. package/latticeai/core/run_explain.py +0 -426
  309. package/latticeai/core/run_store.py +0 -252
  310. package/latticeai/core/timezones.py +0 -80
  311. package/latticeai/core/workspace_computer_memory.py +0 -84
  312. package/latticeai/core/workspace_graph_trace.py +0 -155
  313. package/latticeai/core/workspace_indexing.py +0 -102
  314. package/latticeai/core/workspace_memory.py +0 -77
  315. package/latticeai/core/workspace_onboarding.py +0 -104
  316. package/latticeai/core/workspace_os.py +0 -978
  317. package/latticeai/core/workspace_os_constants.py +0 -126
  318. package/latticeai/core/workspace_os_state.py +0 -180
  319. package/latticeai/core/workspace_os_utils.py +0 -103
  320. package/latticeai/core/workspace_permissions.py +0 -101
  321. package/latticeai/core/workspace_plugins.py +0 -97
  322. package/latticeai/core/workspace_relationships.py +0 -99
  323. package/latticeai/core/workspace_reorganization.py +0 -335
  324. package/latticeai/core/workspace_review_items.py +0 -112
  325. package/latticeai/core/workspace_runs.py +0 -726
  326. package/latticeai/core/workspace_skills.py +0 -109
  327. package/latticeai/core/workspace_snapshots.py +0 -198
  328. package/latticeai/core/workspace_timeline.py +0 -110
  329. package/latticeai/integrations/__init__.py +0 -0
  330. package/latticeai/integrations/telegram_bot/__init__.py +0 -123
  331. package/latticeai/integrations/telegram_bot/__main__.py +0 -17
  332. package/latticeai/integrations/telegram_bot/config.py +0 -86
  333. package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
  334. package/latticeai/integrations/telegram_bot/flows.py +0 -478
  335. package/latticeai/integrations/telegram_bot/helpers.py +0 -322
  336. package/latticeai/integrations/telegram_bot/screens.py +0 -394
  337. package/latticeai/runtime/audit_runtime.py +0 -76
  338. package/latticeai/runtime/automation_runtime.py +0 -81
  339. package/latticeai/runtime/chat_wiring.py +0 -141
  340. package/latticeai/runtime/context_runtime.py +0 -66
  341. package/latticeai/runtime/feature_toggle_wiring.py +0 -157
  342. package/latticeai/runtime/history_runtime.py +0 -163
  343. package/latticeai/runtime/history_writer.py +0 -138
  344. package/latticeai/runtime/hooks_runtime.py +0 -77
  345. package/latticeai/runtime/model_wiring.py +0 -68
  346. package/latticeai/runtime/namespace_runtime.py +0 -163
  347. package/latticeai/runtime/network_boundary_wiring.py +0 -117
  348. package/latticeai/runtime/network_config_runtime.py +0 -56
  349. package/latticeai/runtime/permission_mode_wiring.py +0 -112
  350. package/latticeai/runtime/persistence_runtime.py +0 -159
  351. package/latticeai/runtime/platform_runtime_wiring.py +0 -89
  352. package/latticeai/runtime/review_wiring.py +0 -42
  353. package/latticeai/runtime/router_registration.py +0 -693
  354. package/latticeai/runtime/service_singletons.py +0 -55
  355. package/latticeai/runtime/sso_config_runtime.py +0 -128
  356. package/latticeai/runtime/user_key_runtime.py +0 -106
  357. package/latticeai/runtime/web_runtime.py +0 -92
  358. package/latticeai/server_app.py +0 -51
  359. package/latticeai/services/app_context.py +0 -130
  360. package/latticeai/services/automation_execution.py +0 -266
  361. package/latticeai/services/automation_intelligence.py +0 -614
  362. package/latticeai/services/brain_automation.py +0 -191
  363. package/latticeai/services/brain_intelligence/__init__.py +0 -58
  364. package/latticeai/services/brain_intelligence/_contract.py +0 -71
  365. package/latticeai/services/brain_intelligence/consistency.py +0 -193
  366. package/latticeai/services/brain_intelligence/constants.py +0 -47
  367. package/latticeai/services/brain_intelligence/digest.py +0 -258
  368. package/latticeai/services/brain_intelligence/health.py +0 -331
  369. package/latticeai/services/brain_intelligence/proposals.py +0 -259
  370. package/latticeai/services/brain_intelligence/sampling.py +0 -84
  371. package/latticeai/services/brain_intelligence/service.py +0 -48
  372. package/latticeai/services/change_proposals.py +0 -471
  373. package/latticeai/services/chat_service.py +0 -243
  374. package/latticeai/services/chronicle.py +0 -555
  375. package/latticeai/services/cloud_egress_audit.py +0 -85
  376. package/latticeai/services/cloud_extraction.py +0 -129
  377. package/latticeai/services/cloud_streaming.py +0 -268
  378. package/latticeai/services/cloud_token_guard.py +0 -84
  379. package/latticeai/services/command_center.py +0 -548
  380. package/latticeai/services/evidence_actions.py +0 -258
  381. package/latticeai/services/feature_toggles.py +0 -502
  382. package/latticeai/services/folder_watch.py +0 -520
  383. package/latticeai/services/funnel_metrics.py +0 -307
  384. package/latticeai/services/hybrid_chat.py +0 -316
  385. package/latticeai/services/hybrid_context.py +0 -228
  386. package/latticeai/services/hybrid_policy.py +0 -129
  387. package/latticeai/services/interop_bridges.py +0 -978
  388. package/latticeai/services/local_knowledge.py +0 -465
  389. package/latticeai/services/memory_service/__init__.py +0 -52
  390. package/latticeai/services/memory_service/_contract.py +0 -100
  391. package/latticeai/services/memory_service/brief.py +0 -431
  392. package/latticeai/services/memory_service/constants.py +0 -57
  393. package/latticeai/services/memory_service/maintenance.py +0 -138
  394. package/latticeai/services/memory_service/manager.py +0 -186
  395. package/latticeai/services/memory_service/proof.py +0 -136
  396. package/latticeai/services/memory_service/recall.py +0 -225
  397. package/latticeai/services/memory_service/service.py +0 -48
  398. package/latticeai/services/memory_service/stores.py +0 -110
  399. package/latticeai/services/mode_store.py +0 -132
  400. package/latticeai/services/model_recommendation.py +0 -224
  401. package/latticeai/services/model_runtime/cloud.py +0 -87
  402. package/latticeai/services/network_boundary_service.py +0 -117
  403. package/latticeai/services/obsidian_bridge.py +0 -609
  404. package/latticeai/services/openai_compatible_adapter.py +0 -101
  405. package/latticeai/services/permission_mode_service.py +0 -122
  406. package/latticeai/services/platform_runtime.py +0 -366
  407. package/latticeai/services/review_queue.py +0 -380
  408. package/latticeai/services/router_context.py +0 -59
  409. package/latticeai/services/run_executor.py +0 -387
  410. package/latticeai/services/self_model_service.py +0 -171
  411. package/latticeai/services/setup_detection.py +0 -147
  412. package/latticeai/services/triggers.py +0 -378
  413. package/latticeai/services/upload_service.py +0 -172
  414. package/latticeai/services/workspace_service.py +0 -165
  415. package/latticeai/setup/__init__.py +0 -25
  416. package/latticeai/setup/auto_setup.py +0 -846
  417. package/latticeai/setup/demo_corpus.py +0 -98
  418. package/latticeai/setup/wizard/__init__.py +0 -126
  419. package/latticeai/setup/wizard/catalog.py +0 -175
  420. package/latticeai/setup/wizard/detect.py +0 -302
  421. package/latticeai/setup/wizard/install.py +0 -348
  422. package/latticeai/setup/wizard/paths.py +0 -165
  423. package/latticeai/setup/wizard/plans.py +0 -74
  424. package/latticeai/setup/wizard/recommend.py +0 -320
  425. package/scripts/bench_agent_smoke.py +0 -409
  426. package/scripts/bench_models.py +0 -540
  427. package/scripts/bench_vector_index.py +0 -295
  428. package/scripts/funnel_soft_gate.py +0 -192
  429. package/scripts/generate_agent_loop_fixtures.py +0 -994
  430. package/scripts/generate_rust_parity_fixtures.py +0 -908
  431. package/scripts/migrate_brain_storage.py +0 -57
  432. package/scripts/parity_fixture_corpus_context.py +0 -162
  433. package/scripts/parity_fixture_corpus_docgen.py +0 -341
  434. package/scripts/profile_kg.py +0 -355
  435. package/server.py +0 -30
  436. package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
  437. package/static/app/assets/Capture-DiQ219jW.js +0 -1
  438. package/static/app/assets/System-CieofHQa.js +0 -1
  439. package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
  440. package/static/app/assets/circle-check-qqLug9nU.js +0 -1
  441. package/static/app/assets/index-DxmOfNRi.css +0 -2
  442. package/static/app/assets/primitives-SNp0LRJz.js +0 -1
  443. package/static/app/assets/search-BcHqkjoy.js +0 -1
@@ -1,90 +0,0 @@
1
- """The seam the three ingestion mixins share.
2
-
3
- ``IngestionPipeline`` is assembled from the routing, folder and jobs mixins.
4
- Each of them reads constructor state it does not own and calls back into
5
- ``ingest`` — the point of the split is that "how one picture is stored" and
6
- "how a folder is walked" stop sharing a thousand-line file, not that they stop
7
- sharing ``self``.
8
-
9
- Typing-only, exactly like :mod:`lattice_brain.graph._kg_contract`: at runtime
10
- the mixins alias it to ``object``, so the MRO and every method resolution stay
11
- byte-for-byte what the single-file class had. Adding a cross-mixin call without
12
- declaring it here is a type error.
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- from pathlib import Path
18
- from typing import Any, Dict, List, Optional
19
-
20
- from ..ingestion_jobs import BackgroundIngestionJob, BackgroundIngestionQueue
21
- from ..multimodal import MultimodalPorts
22
- from .models import IngestionItem, IngestionResult
23
-
24
-
25
- class IngestionCore:
26
- """What any ingestion mixin may assume about ``self``.
27
-
28
- Never instantiated and never inherited at runtime — see the module
29
- docstring. Members are declared, not implemented: the implementation lives
30
- in ``pipeline.py`` or in whichever mixin owns it.
31
- """
32
-
33
- # ── State owned by IngestionPipeline.__init__ ────────────────────────────
34
- _kg: Any
35
- _hooks: Any
36
- _enable: bool
37
- _audit: Optional[Any]
38
- _max_text_bytes: int
39
- _pipeline_name: str
40
- _bg_queue: BackgroundIngestionQueue
41
- _auto_vector_index_opt_in: bool
42
- _multimodal_opt_in: bool
43
- _multimodal: MultimodalPorts
44
- _keyframes: int
45
-
46
- # ── pipeline.py: the gates, resolved when they are asked ─────────────────
47
- @property
48
- def _allow_multimodal(self) -> bool:
49
- raise NotImplementedError
50
-
51
- @property
52
- def _allow_video(self) -> bool:
53
- raise NotImplementedError
54
-
55
- # ── pipeline.py: the one door every mixin routes back through ────────────
56
- def available(self) -> bool:
57
- raise NotImplementedError
58
-
59
- def ingest(
60
- self, item: IngestionItem, *, user_email: Optional[str] = None
61
- ) -> IngestionResult:
62
- raise NotImplementedError
63
-
64
- # ── routing.py: reached from the folder walk and the modality doors ──────
65
- def _resolve_file_path(self, item: IngestionItem) -> Path:
66
- raise NotImplementedError
67
-
68
- # ── folders.py: the folder-scan allow-list the multimodal gates widen ────
69
- def _folder_extensions(self) -> frozenset:
70
- raise NotImplementedError
71
-
72
- # ── jobs_api.py: scheduling, reached from the folder walk ────────────────
73
- def schedule_background(
74
- self,
75
- items: List[IngestionItem],
76
- *,
77
- incremental: bool = True,
78
- user_email: Optional[str] = None,
79
- ) -> BackgroundIngestionJob:
80
- raise NotImplementedError
81
-
82
- def run_background_job(
83
- self, job_id: str, *, user_email: Optional[str] = None
84
- ) -> Dict[str, Any]:
85
- raise NotImplementedError
86
-
87
- def _execute_background_job(
88
- self, job: BackgroundIngestionJob, *, user_email: Optional[str] = None
89
- ) -> Dict[str, Any]:
90
- raise NotImplementedError
@@ -1,57 +0,0 @@
1
- """``.latticeignore`` parsing and matching for the folder walk.
2
-
3
- A gitignore-like subset: blank lines and ``#`` comments are dropped, patterns
4
- are ``fnmatch`` globs, and a trailing ``/`` restricts a pattern to directories.
5
- Patterns match against both the root-relative posix path and the basename, so
6
- ``*.log`` and ``docs/draft.md`` both behave the way a reader expects.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import fnmatch
12
- from pathlib import Path
13
- from typing import Iterable, List
14
-
15
- from .constants import LATTICEIGNORE_FILENAME
16
-
17
-
18
- def _load_latticeignore(root: Path) -> List[str]:
19
- """Parse ``root/.latticeignore`` → glob patterns (gitignore-like subset)."""
20
- ignore_file = root / LATTICEIGNORE_FILENAME
21
- patterns: List[str] = []
22
- if not ignore_file.is_file():
23
- return patterns
24
- try:
25
- lines = ignore_file.read_text(encoding="utf-8", errors="ignore").splitlines()
26
- except OSError:
27
- return patterns
28
- for raw in lines:
29
- line = raw.strip()
30
- if not line or line.startswith("#"):
31
- continue
32
- patterns.append(line)
33
- return patterns
34
-
35
-
36
- def _matches_ignore(
37
- rel_posix: str, name: str, *, is_dir: bool, patterns: Iterable[str]
38
- ) -> bool:
39
- """fnmatch-based .latticeignore matching.
40
-
41
- - ``pattern/`` matches directories only (files under it never appear
42
- because ignored directories are pruned during the walk).
43
- - Patterns match against both the root-relative posix path and the
44
- basename, so ``*.log`` and ``docs/draft.md`` both behave as expected.
45
- """
46
- for raw in patterns:
47
- pattern = raw
48
- if pattern.endswith("/"):
49
- if not is_dir:
50
- continue
51
- pattern = pattern.rstrip("/")
52
- pattern = pattern.lstrip("/")
53
- if not pattern:
54
- continue
55
- if fnmatch.fnmatch(rel_posix, pattern) or fnmatch.fnmatch(name, pattern):
56
- return True
57
- return False
@@ -1,258 +0,0 @@
1
- """Walking a folder, and the one-page web hand-off, into the standard door.
2
-
3
- Neither method is a second ingest path: both build ordinary ``IngestionItem``
4
- values and hand them to ``IngestionPipeline.ingest``. The folder walk owns the
5
- filtering order (hard skip-list → hidden → ``.latticeignore`` → extension →
6
- size) and the choice between ingesting inline and scheduling in the background;
7
- the web hand-off owns the refusal to fetch or parse anything itself.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import os
13
- from pathlib import Path
14
- from typing import Any, Dict, Iterable, List, Optional
15
-
16
- from ._contract import IngestionCore as _Core
17
- from .constants import (
18
- DEFAULT_FOLDER_EXTENSIONS,
19
- DEFAULT_MAX_FILE_BYTES,
20
- FOLDER_DEFAULT_SKIP_DIRS,
21
- FOLDER_DOCUMENT_EXTENSIONS,
22
- FOLDER_MULTIMODAL_EXTENSIONS,
23
- FOLDER_VIDEO_EXTENSIONS,
24
- LATTICEIGNORE_FILENAME,
25
- )
26
- from .folder_scan import _load_latticeignore, _matches_ignore
27
- from .models import IngestionItem, IngestionResult
28
-
29
-
30
- class IngestionFolderMixin(_Core):
31
- """Folder walk + web hand-off. Mixed into ``IngestionPipeline``."""
32
-
33
- def ingest_web_page(
34
- self,
35
- url: str,
36
- extracted_text: str,
37
- *,
38
- title: Optional[str] = None,
39
- metadata: Optional[Dict[str, Any]] = None,
40
- owner: Optional[str] = None,
41
- workspace_id: Optional[str] = None,
42
- captured_at: Optional[str] = None,
43
- user_email: Optional[str] = None,
44
- ) -> IngestionResult:
45
- """Ingest an *already-extracted* web page (see module docstring seam).
46
-
47
- Fetching/parsing is upstream's responsibility (browser extension /
48
- tools layer); this wrapper only normalizes ``(url, extracted_text)``
49
- into an ``IngestionItem(source_type="web_url")`` and routes it through
50
- the standard :meth:`ingest` door.
51
- """
52
- url = str(url or "").strip()
53
- if not url:
54
- return IngestionResult(
55
- status="failed", source_type="web_url",
56
- indexing_status="skipped", detail="url required",
57
- )
58
- text = str(extracted_text or "")
59
- if not text.strip():
60
- return IngestionResult(
61
- status="failed", source_type="web_url",
62
- indexing_status="skipped",
63
- detail=(
64
- "extracted_text required — the graph layer does not fetch or "
65
- "parse the web; extraction happens upstream."
66
- ),
67
- )
68
- item = IngestionItem(
69
- source_type="web_url",
70
- title=title or url,
71
- text=text,
72
- source_uri=url,
73
- owner=owner,
74
- workspace_id=workspace_id,
75
- captured_at=captured_at,
76
- metadata=dict(metadata or {}),
77
- )
78
- return self.ingest(item, user_email=user_email or owner)
79
-
80
- def ingest_folder(
81
- self,
82
- root_path: Any,
83
- *,
84
- recursive: bool = True,
85
- background: bool = False,
86
- extensions: Optional[Iterable[str]] = None,
87
- max_file_bytes: int = DEFAULT_MAX_FILE_BYTES,
88
- include_hidden: bool = False,
89
- max_files: int = 1000,
90
- max_errors: int = 25,
91
- owner: Optional[str] = None,
92
- workspace_id: Optional[str] = None,
93
- user_email: Optional[str] = None,
94
- ) -> Dict[str, Any]:
95
- """Walk ``root_path`` and ingest every eligible file through the pipeline.
96
-
97
- Filtering, in order: hard skip-list directories (``.git`` …), hidden
98
- entries (unless ``include_hidden``), root ``.latticeignore`` patterns
99
- (fnmatch globs; ``dir/`` suffix prunes directories), extension
100
- allow-list, then ``max_file_bytes``. Text/code files are read inline so
101
- their content is chunked; ``.pdf`` routes through the file door without
102
- inline extraction.
103
-
104
- ``background=True`` schedules the built items on the existing
105
- :class:`BackgroundIngestionQueue` instead of ingesting inline.
106
- Returns a summary dict with counts and per-file errors (capped at
107
- ``max_errors``).
108
- """
109
- summary: Dict[str, Any] = {
110
- "root": str(root_path),
111
- "recursive": bool(recursive),
112
- "background": bool(background),
113
- "scanned": 0,
114
- "matched": 0,
115
- "ingested": 0,
116
- "duplicate": 0,
117
- "failed": 0,
118
- "skipped": {"ignored": 0, "extension": 0, "too_large": 0, "hidden": 0},
119
- "truncated": False,
120
- "errors": [],
121
- }
122
- try:
123
- root = Path(root_path).expanduser()
124
- except TypeError:
125
- summary.update(status="failed", detail=f"invalid root path: {root_path!r}")
126
- return summary
127
- if not root.is_dir():
128
- summary.update(status="failed", detail=f"not a directory: {root}")
129
- return summary
130
- if not self.available():
131
- summary.update(
132
- status="unavailable",
133
- detail="Knowledge Graph is disabled (LATTICEAI_ENABLE_GRAPH).",
134
- )
135
- return summary
136
- summary["root"] = str(root)
137
- max_files = max(1, int(max_files))
138
- max_errors = max(0, int(max_errors))
139
- max_file_bytes = max(1, int(max_file_bytes))
140
- allowed_exts = (
141
- frozenset(str(e).lower() if str(e).startswith(".") else f".{str(e).lower()}" for e in extensions)
142
- if extensions
143
- else self._folder_extensions()
144
- )
145
- patterns = _load_latticeignore(root)
146
- errors: List[Dict[str, Any]] = summary["errors"]
147
- skipped = summary["skipped"]
148
- items: List[IngestionItem] = []
149
-
150
- def _record_error(path: Path, detail: str, status: str = "failed") -> None:
151
- summary["failed"] += 1
152
- if len(errors) < max_errors:
153
- errors.append({"path": str(path), "status": status, "detail": detail})
154
-
155
- for dirpath, dirnames, filenames in os.walk(root):
156
- current = Path(dirpath)
157
- rel_dir = current.relative_to(root)
158
- kept_dirs: List[str] = []
159
- for name in sorted(dirnames):
160
- if name in FOLDER_DEFAULT_SKIP_DIRS:
161
- continue
162
- if name.startswith(".") and not include_hidden:
163
- continue
164
- rel = name if str(rel_dir) == "." else (rel_dir / name).as_posix()
165
- if _matches_ignore(rel, name, is_dir=True, patterns=patterns):
166
- skipped["ignored"] += 1
167
- continue
168
- kept_dirs.append(name)
169
- dirnames[:] = kept_dirs if recursive else []
170
-
171
- for name in sorted(filenames):
172
- if name == LATTICEIGNORE_FILENAME:
173
- continue
174
- summary["scanned"] += 1
175
- path = current / name
176
- rel = name if str(rel_dir) == "." else (rel_dir / name).as_posix()
177
- if name.startswith(".") and not include_hidden:
178
- skipped["hidden"] += 1
179
- continue
180
- if _matches_ignore(rel, name, is_dir=False, patterns=patterns):
181
- skipped["ignored"] += 1
182
- continue
183
- ext = path.suffix.lower()
184
- if ext not in allowed_exts:
185
- skipped["extension"] += 1
186
- continue
187
- try:
188
- size = path.stat().st_size
189
- except OSError as exc:
190
- _record_error(path, f"stat failed: {exc}")
191
- continue
192
- if size > max_file_bytes:
193
- skipped["too_large"] += 1
194
- continue
195
- if len(items) >= max_files:
196
- summary["truncated"] = True
197
- break
198
- item_metadata: Dict[str, Any] = {"relative_path": rel}
199
- if ext in (FOLDER_MULTIMODAL_EXTENSIONS | FOLDER_VIDEO_EXTENSIONS) and self._allow_multimodal:
200
- # Routed by modality inside ``ingest``; reading the bytes as
201
- # UTF-8 here would only produce mojibake.
202
- source_type = "file"
203
- elif ext in FOLDER_DOCUMENT_EXTENSIONS:
204
- source_type = "pdf"
205
- else:
206
- source_type = "file"
207
- try:
208
- content = path.read_text(encoding="utf-8", errors="ignore")
209
- except OSError as exc:
210
- _record_error(path, f"read failed: {exc}")
211
- continue
212
- item_metadata["extracted"] = {"content": content, "chars": len(content)}
213
- items.append(
214
- IngestionItem(
215
- source_type=source_type,
216
- title=name,
217
- path=str(path),
218
- source_uri=str(path),
219
- owner=owner,
220
- workspace_id=workspace_id,
221
- metadata=item_metadata,
222
- )
223
- )
224
- if summary["truncated"]:
225
- break
226
-
227
- summary["matched"] = len(items)
228
- if background:
229
- job = self.schedule_background(
230
- items, incremental=True, user_email=user_email or owner,
231
- )
232
- summary.update(status="scheduled", job_id=job.job_id, scheduled=len(items))
233
- return summary
234
-
235
- for item in items:
236
- result = self.ingest(item, user_email=user_email or owner)
237
- if result.status == "ok":
238
- if result.duplicate:
239
- summary["duplicate"] += 1
240
- else:
241
- summary["ingested"] += 1
242
- else:
243
- _record_error(Path(item.path or ""), result.detail or result.status, result.status)
244
- summary["status"] = "ok" if summary["failed"] == 0 else "partial"
245
- return summary
246
-
247
- def _folder_extensions(self) -> frozenset:
248
- """Folder-scan allow-list — pictures, recordings and films when enabled.
249
-
250
- Video joins only when this machine can actually decode one, so a scan
251
- never fills the error list with files it was always going to refuse.
252
- """
253
- if not self._allow_multimodal:
254
- return DEFAULT_FOLDER_EXTENSIONS
255
- allowed = DEFAULT_FOLDER_EXTENSIONS | FOLDER_MULTIMODAL_EXTENSIONS
256
- if self._allow_video:
257
- return allowed | FOLDER_VIDEO_EXTENSIONS
258
- return allowed
@@ -1,107 +0,0 @@
1
- """Scheduling many items, and running (or resuming) what was scheduled.
2
-
3
- The pipeline owns "ingest one item"; ``BackgroundIngestionQueue`` owns
4
- "schedule many and report progress". This mixin is the seam between them:
5
- per-item errors are recorded and never abort a job, progress is checkpointed
6
- after every item, and the same method powers both the first run and a resume
7
- because already-completed items are simply skipped.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- from typing import Any, Dict, List, Optional
13
-
14
- from ..ingestion_jobs import BackgroundIngestionJob
15
- from ._contract import IngestionCore as _Core
16
- from .models import IngestionItem
17
-
18
-
19
- class IngestionJobsMixin(_Core):
20
- """Background job scheduling. Mixed into ``IngestionPipeline``."""
21
-
22
- # --- Large candidate #1: background / incremental scheduling (slice) ---
23
- def schedule_background(
24
- self,
25
- items: List[IngestionItem],
26
- *,
27
- incremental: bool = True,
28
- user_email: Optional[str] = None,
29
- ) -> BackgroundIngestionJob:
30
- """Schedule items for background incremental indexing.
31
-
32
- Returns a job handle. Actual execution can be driven by caller
33
- (or future worker) calling pipeline.ingest on each — or through
34
- :meth:`run_background_job`. This seam enables large-corpus scale
35
- without blocking user requests.
36
- """
37
- job = self._bg_queue.schedule(items, incremental=incremental, user_email=user_email)
38
- # mark initial status on results concept (jobs track)
39
- return job
40
-
41
- def get_background_job(self, job_id: str) -> Optional[BackgroundIngestionJob]:
42
- return self._bg_queue.get(job_id)
43
-
44
- def list_background_jobs(self, limit: int = 20) -> List[Dict[str, Any]]:
45
- """Recent jobs (newest first) in the frozen ``/api/ingestion`` schema."""
46
- return [job.as_dict() for job in self._bg_queue.list_recent(limit=limit)]
47
-
48
- def run_background_job(
49
- self, job_id: str, *, user_email: Optional[str] = None
50
- ) -> Dict[str, Any]:
51
- """Execute a queued/interrupted job's remaining items.
52
-
53
- Per-item errors are recorded (capped) and never abort the job. The
54
- final status is ``completed`` (all done), ``partial`` (some done),
55
- or ``failed`` (nothing done). Already-completed items are skipped, so
56
- the same method safely powers both first-run and resume.
57
- """
58
- job = self._bg_queue.get(job_id)
59
- if job is None:
60
- return {"status": "not_found", "job_id": job_id}
61
- if job.status == "running":
62
- return job.as_dict()
63
- return self._execute_background_job(job, user_email=user_email)
64
-
65
- def resume_background_job(
66
- self, job_id: str, *, user_email: Optional[str] = None
67
- ) -> Dict[str, Any]:
68
- """Resume an interrupted/partial/failed job from its remaining items."""
69
- return self.run_background_job(job_id, user_email=user_email)
70
-
71
- def _execute_background_job(
72
- self, job: BackgroundIngestionJob, *, user_email: Optional[str] = None
73
- ) -> Dict[str, Any]:
74
- job.status = "running"
75
- # Retried items get a fresh verdict: reset failure state for this run.
76
- job.failed = 0
77
- job.errors = []
78
- job.touch()
79
- self._bg_queue.save(job)
80
- runner_email = user_email or job.user_email
81
- for index in job.remaining_indices():
82
- item = job.items[index]
83
- try:
84
- result = self.ingest(item, user_email=runner_email or item.owner)
85
- status, detail = result.status, result.detail
86
- except Exception as exc: # noqa: BLE001 — per-item isolation: keep going
87
- status, detail = "failed", str(exc)
88
- if status == "ok":
89
- job.done_indices.add(index)
90
- else:
91
- job.record_error(index, item, detail or status)
92
- job.processed = len(job.done_indices)
93
- job.touch()
94
- # Checkpoint per item: a crash here must cost at most the item in
95
- # flight, never the whole job's progress. One small UPDATE against
96
- # an ingest (parse + chunk + embed) is noise.
97
- self._bg_queue.save(job)
98
- job.processed = len(job.done_indices)
99
- if job.total == 0 or job.processed >= job.total:
100
- job.status = "completed"
101
- elif job.processed > 0:
102
- job.status = "partial"
103
- else:
104
- job.status = "failed"
105
- job.touch()
106
- self._bg_queue.save(job)
107
- return job.as_dict()