marvisx-cli 0.1.0__py3-none-any.whl

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 (587) hide show
  1. core/api/__init__.py +0 -0
  2. core/api/agents/__init__.py +0 -0
  3. core/api/agents/session_health.py +59 -0
  4. core/api/agents/session_manager.py +206 -0
  5. core/api/bin/marvisx-state-hook.py +182 -0
  6. core/api/config.py +533 -0
  7. core/api/db.py +1516 -0
  8. core/api/dependencies/__init__.py +0 -0
  9. core/api/dependencies/tenant.py +34 -0
  10. core/api/main.py +1641 -0
  11. core/api/mcp/__init__.py +8 -0
  12. core/api/mcp/_adapter.py +184 -0
  13. core/api/mcp/server.py +58 -0
  14. core/api/mcp/tools/__init__.py +59 -0
  15. core/api/mcp/tools/brain.py +599 -0
  16. core/api/mcp/tools/graph.py +380 -0
  17. core/api/mcp/tools/handoffs.py +112 -0
  18. core/api/mcp/tools/ingest.py +326 -0
  19. core/api/mcp/tools/learnings.py +144 -0
  20. core/api/mcp/tools/projects.py +99 -0
  21. core/api/mcp/tools/pull_requests.py +173 -0
  22. core/api/mcp/tools/safety.py +111 -0
  23. core/api/mcp/tools/search.py +79 -0
  24. core/api/mcp/tools/tasks.py +258 -0
  25. core/api/middleware/__init__.py +0 -0
  26. core/api/middleware/tool_call_audit.py +111 -0
  27. core/api/models/__init__.py +346 -0
  28. core/api/models/auth.py +48 -0
  29. core/api/models/brain.py +1006 -0
  30. core/api/models/common.py +76 -0
  31. core/api/models/costs.py +91 -0
  32. core/api/models/graph.py +66 -0
  33. core/api/models/graph_cosmo.py +125 -0
  34. core/api/models/graph_pr_impact.py +257 -0
  35. core/api/models/graph_ux.py +141 -0
  36. core/api/models/inbox.py +230 -0
  37. core/api/models/ingest_keys.py +108 -0
  38. core/api/models/kg.py +41 -0
  39. core/api/models/llm_config.py +56 -0
  40. core/api/models/monitoring.py +234 -0
  41. core/api/models/projects.py +161 -0
  42. core/api/models/search.py +42 -0
  43. core/api/models/sessions.py +322 -0
  44. core/api/models/tasks.py +184 -0
  45. core/api/models/teams.py +63 -0
  46. core/api/models/users.py +184 -0
  47. core/api/observability/__init__.py +0 -0
  48. core/api/observability/tracing.py +92 -0
  49. core/api/paths.py +26 -0
  50. core/api/rate_limit.py +24 -0
  51. core/api/rbac.py +112 -0
  52. core/api/routers/__init__.py +0 -0
  53. core/api/routers/_adapter.py +24 -0
  54. core/api/routers/admin_pr_impact.py +230 -0
  55. core/api/routers/admin_settings.py +147 -0
  56. core/api/routers/agent.py +1079 -0
  57. core/api/routers/agent_tokens.py +276 -0
  58. core/api/routers/app_settings.py +112 -0
  59. core/api/routers/audit.py +89 -0
  60. core/api/routers/auth.py +586 -0
  61. core/api/routers/bench.py +161 -0
  62. core/api/routers/brain.py +881 -0
  63. core/api/routers/brain_directions.py +527 -0
  64. core/api/routers/ci_checks.py +140 -0
  65. core/api/routers/comments.py +273 -0
  66. core/api/routers/costs.py +148 -0
  67. core/api/routers/docs_coverage.py +217 -0
  68. core/api/routers/docs_governance.py +63 -0
  69. core/api/routers/documents.py +318 -0
  70. core/api/routers/files.py +163 -0
  71. core/api/routers/finder.py +987 -0
  72. core/api/routers/graph.py +836 -0
  73. core/api/routers/handoffs.py +156 -0
  74. core/api/routers/inbox.py +496 -0
  75. core/api/routers/ingest_api_keys.py +205 -0
  76. core/api/routers/ingest_triage.py +1227 -0
  77. core/api/routers/judge.py +306 -0
  78. core/api/routers/kg.py +336 -0
  79. core/api/routers/learnings.py +253 -0
  80. core/api/routers/llm_config.py +130 -0
  81. core/api/routers/monitoring.py +347 -0
  82. core/api/routers/notifications.py +125 -0
  83. core/api/routers/pr_impact.py +315 -0
  84. core/api/routers/projects.py +1061 -0
  85. core/api/routers/pull_requests.py +312 -0
  86. core/api/routers/push.py +67 -0
  87. core/api/routers/raci.py +228 -0
  88. core/api/routers/search.py +125 -0
  89. core/api/routers/sessions.py +3100 -0
  90. core/api/routers/settings.py +90 -0
  91. core/api/routers/share_repo.py +68 -0
  92. core/api/routers/status_updates.py +96 -0
  93. core/api/routers/tags.py +45 -0
  94. core/api/routers/tasks.py +526 -0
  95. core/api/routers/teams.py +425 -0
  96. core/api/routers/terminal.py +105 -0
  97. core/api/routers/users.py +331 -0
  98. core/api/routers/webhooks.py +330 -0
  99. core/api/runtime_settings.py +84 -0
  100. core/api/security.py +652 -0
  101. core/api/services/__init__.py +0 -0
  102. core/api/services/audit.py +58 -0
  103. core/api/services/auto_approval.py +82 -0
  104. core/api/services/brain/__init__.py +52 -0
  105. core/api/services/brain/baseline.py +230 -0
  106. core/api/services/brain/capabilities.py +75 -0
  107. core/api/services/brain/cascade_rollup.py +388 -0
  108. core/api/services/brain/compound_bridge.py +215 -0
  109. core/api/services/brain/cycle.py +1242 -0
  110. core/api/services/brain/cycle_snapshot.py +371 -0
  111. core/api/services/brain/digest_collector.py +147 -0
  112. core/api/services/brain/direction.py +421 -0
  113. core/api/services/brain/drift.py +356 -0
  114. core/api/services/brain/drift_router.py +409 -0
  115. core/api/services/brain/edge_metrics.py +79 -0
  116. core/api/services/brain/events_reader.py +222 -0
  117. core/api/services/brain/findings.py +1379 -0
  118. core/api/services/brain/findings_reader.py +1006 -0
  119. core/api/services/brain/jobs.py +733 -0
  120. core/api/services/brain/journal.py +206 -0
  121. core/api/services/brain/knowledge_forms.py +92 -0
  122. core/api/services/brain/llm/__init__.py +37 -0
  123. core/api/services/brain/llm/_runner.py +62 -0
  124. core/api/services/brain/llm/base.py +70 -0
  125. core/api/services/brain/llm/cache.py +99 -0
  126. core/api/services/brain/llm/constants.py +46 -0
  127. core/api/services/brain/llm/direction_alignment.py +289 -0
  128. core/api/services/brain/llm/factory.py +132 -0
  129. core/api/services/brain/llm/finding_reasoning.py +98 -0
  130. core/api/services/brain/llm/finding_summary.py +92 -0
  131. core/api/services/brain/llm/grounding.py +46 -0
  132. core/api/services/brain/llm/journal_polish.py +96 -0
  133. core/api/services/brain/llm/local_gateway.py +426 -0
  134. core/api/services/brain/llm/parsers.py +71 -0
  135. core/api/services/brain/llm/router_glue.py +422 -0
  136. core/api/services/brain/memory_ops.py +1677 -0
  137. core/api/services/brain/models.py +140 -0
  138. core/api/services/brain/owner_hint.py +211 -0
  139. core/api/services/brain/recap.py +307 -0
  140. core/api/services/brain/rules/__init__.py +65 -0
  141. core/api/services/brain/rules/_signals.py +205 -0
  142. core/api/services/brain/rules/dr1_activity_without_status.py +108 -0
  143. core/api/services/brain/rules/dr2_decision_without_adr.py +110 -0
  144. core/api/services/brain/rules/dr3_stale_open_loop.py +127 -0
  145. core/api/services/brain/rules/dr4_docs_governance_drift.py +79 -0
  146. core/api/services/brain/rules/dr5_playbook_changed.py +99 -0
  147. core/api/services/brain/rules/dr6_external_update_unpropagated.py +100 -0
  148. core/api/services/brain/rules/dr7_claimed_decision_gap.py +123 -0
  149. core/api/services/brain/rules/dr8_direction_misalignment.py +230 -0
  150. core/api/services/brain/runs_reader.py +485 -0
  151. core/api/services/brain/scope.py +79 -0
  152. core/api/services/brain/sources/__init__.py +46 -0
  153. core/api/services/brain/sources/base.py +86 -0
  154. core/api/services/brain/sources/git_kg.py +393 -0
  155. core/api/services/brain/sources/handoffs.py +157 -0
  156. core/api/services/brain/sources/ingestor.py +130 -0
  157. core/api/services/brain/sources/learnings.py +121 -0
  158. core/api/services/brain/sources/pir_tasks.py +245 -0
  159. core/api/services/brain/watermarks.py +147 -0
  160. core/api/services/brain/ws_emitter.py +170 -0
  161. core/api/services/cc_tasks_reader.py +76 -0
  162. core/api/services/ci_service.py +263 -0
  163. core/api/services/claude_metrics.py +796 -0
  164. core/api/services/codex_metrics.py +364 -0
  165. core/api/services/conversation_reader.py +102 -0
  166. core/api/services/cost_service.py +243 -0
  167. core/api/services/crypto.py +147 -0
  168. core/api/services/docs_governance/__init__.py +1 -0
  169. core/api/services/docs_governance/confidence.py +230 -0
  170. core/api/services/docs_governance/config.py +87 -0
  171. core/api/services/docs_governance/enrichment.py +65 -0
  172. core/api/services/docs_governance/frontmatter_validator.py +83 -0
  173. core/api/services/docs_governance/hard_gates.py +221 -0
  174. core/api/services/docs_governance/triage_orchestrator.py +98 -0
  175. core/api/services/embedding_internal.py +395 -0
  176. core/api/services/embedding_service.py +832 -0
  177. core/api/services/event_dispatcher.py +167 -0
  178. core/api/services/events.py +70 -0
  179. core/api/services/git_ops.py +621 -0
  180. core/api/services/graph_cosmo_service.py +440 -0
  181. core/api/services/graph_ranker.py +306 -0
  182. core/api/services/graph_service.py +1589 -0
  183. core/api/services/inbox.py +800 -0
  184. core/api/services/inbox_digest.py +221 -0
  185. core/api/services/inbox_digest_deep_research.py +80 -0
  186. core/api/services/inbox_digest_jobs.py +595 -0
  187. core/api/services/inbox_gmail_sync.py +167 -0
  188. core/api/services/inbox_llm_classifier.py +906 -0
  189. core/api/services/inbox_source_identity.py +116 -0
  190. core/api/services/inbox_sources.py +456 -0
  191. core/api/services/inbox_taxonomy.py +195 -0
  192. core/api/services/inbox_tldr.py +1079 -0
  193. core/api/services/inbox_triage.py +899 -0
  194. core/api/services/ingest/__init__.py +13 -0
  195. core/api/services/ingest/api_key_auth.py +136 -0
  196. core/api/services/ingest/auto_approve.py +120 -0
  197. core/api/services/ingest/classifier.py +138 -0
  198. core/api/services/ingest/confidence.py +173 -0
  199. core/api/services/ingest/dispatch.py +88 -0
  200. core/api/services/ingest/embedding_router.py +272 -0
  201. core/api/services/ingest/events.py +33 -0
  202. core/api/services/ingest/ignore_patterns.py +79 -0
  203. core/api/services/ingest/image_probe.py +218 -0
  204. core/api/services/ingest/ingress.py +263 -0
  205. core/api/services/ingest/insert_saga.py +793 -0
  206. core/api/services/ingest/llm/__init__.py +13 -0
  207. core/api/services/ingest/llm/anthropic_haiku.py +23 -0
  208. core/api/services/ingest/llm/base.py +59 -0
  209. core/api/services/ingest/llm/byok_provider.py +130 -0
  210. core/api/services/ingest/llm/classification_context.py +301 -0
  211. core/api/services/ingest/llm/config_store.py +246 -0
  212. core/api/services/ingest/llm/factory.py +24 -0
  213. core/api/services/ingest/llm/kg_enricher.py +306 -0
  214. core/api/services/ingest/llm/local_gateway.py +821 -0
  215. core/api/services/ingest/llm/local_vllm.py +23 -0
  216. core/api/services/ingest/llm/openai_nano.py +349 -0
  217. core/api/services/ingest/lock_advisory.py +57 -0
  218. core/api/services/ingest/parser_router.py +1756 -0
  219. core/api/services/ingest/parsers/__init__.py +1 -0
  220. core/api/services/ingest/parsers/docling_parser.py +142 -0
  221. core/api/services/ingest/parsers/docparse_gateway.py +178 -0
  222. core/api/services/ingest/parsers/docx_parser.py +127 -0
  223. core/api/services/ingest/parsers/folder_unpacker.py +85 -0
  224. core/api/services/ingest/parsers/gateway_aux.py +147 -0
  225. core/api/services/ingest/parsers/image_parser.py +251 -0
  226. core/api/services/ingest/parsers/internal_markdown.py +89 -0
  227. core/api/services/ingest/parsers/ocr_gateway.py +117 -0
  228. core/api/services/ingest/parsers/ocr_pdf_parser.py +112 -0
  229. core/api/services/ingest/parsers/pdf_types.py +13 -0
  230. core/api/services/ingest/parsers/transcript_parser.py +445 -0
  231. core/api/services/ingest/parsers/vision_gateway.py +186 -0
  232. core/api/services/ingest/parsers/xlsx_parser.py +91 -0
  233. core/api/services/ingest/parsers/zip_unpacker.py +126 -0
  234. core/api/services/ingest/preflight.py +393 -0
  235. core/api/services/ingest/retry_voyage.py +88 -0
  236. core/api/services/ingest/routing_policy.py +307 -0
  237. core/api/services/ingest/serializers/__init__.py +1 -0
  238. core/api/services/ingest/serializers/xlsx_to_markdown.py +80 -0
  239. core/api/services/ingest/skip_log.py +74 -0
  240. core/api/services/ingest/watcher.py +637 -0
  241. core/api/services/kg/__init__.py +0 -0
  242. core/api/services/kg/audit.py +49 -0
  243. core/api/services/kg/hybrid_search.py +691 -0
  244. core/api/services/kg/lens.py +339 -0
  245. core/api/services/kg/pr_impact.py +770 -0
  246. core/api/services/kg/queries.py +152 -0
  247. core/api/services/kg/ranking.py +89 -0
  248. core/api/services/kg/rrf.py +143 -0
  249. core/api/services/kg_watcher_control.py +161 -0
  250. core/api/services/local_llm/__init__.py +19 -0
  251. core/api/services/local_llm/async_client.py +385 -0
  252. core/api/services/local_llm/client.py +173 -0
  253. core/api/services/local_llm/url_validator.py +44 -0
  254. core/api/services/metrics_collector.py +646 -0
  255. core/api/services/metrics_providers.py +65 -0
  256. core/api/services/model_registry.py +266 -0
  257. core/api/services/model_router.py +137 -0
  258. core/api/services/n8n_client.py +77 -0
  259. core/api/services/newsletter_llm_gateway.py +66 -0
  260. core/api/services/notification_service.py +134 -0
  261. core/api/services/openai_responses.py +55 -0
  262. core/api/services/opencode_metrics.py +375 -0
  263. core/api/services/opencode_sessions.py +173 -0
  264. core/api/services/pii_redactor.py +138 -0
  265. core/api/services/pr_impact_pipeline/__init__.py +21 -0
  266. core/api/services/pr_impact_pipeline/differ.py +421 -0
  267. core/api/services/pr_impact_pipeline/dispatcher.py +415 -0
  268. core/api/services/pr_impact_pipeline/gc.py +93 -0
  269. core/api/services/pr_impact_pipeline/languages.py +192 -0
  270. core/api/services/pr_impact_pipeline/parser.py +178 -0
  271. core/api/services/pr_impact_pipeline/writer.py +394 -0
  272. core/api/services/pr_service.py +1393 -0
  273. core/api/services/project_paths.py +70 -0
  274. core/api/services/project_status_updates.py +265 -0
  275. core/api/services/providers.py +276 -0
  276. core/api/services/push_service.py +170 -0
  277. core/api/services/reminder_service.py +89 -0
  278. core/api/services/runas.py +41 -0
  279. core/api/services/salience_service.py +69 -0
  280. core/api/services/security_collector.py +281 -0
  281. core/api/services/session_catalog.py +385 -0
  282. core/api/services/session_metrics_service.py +301 -0
  283. core/api/services/session_ops.py +272 -0
  284. core/api/services/session_state.py +173 -0
  285. core/api/services/share_links.py +222 -0
  286. core/api/services/task_transitions.py +146 -0
  287. core/api/services/terminal_metrics.py +462 -0
  288. core/api/services/terminal_metrics_dump.py +203 -0
  289. core/api/services/tmux.py +1205 -0
  290. core/api/services/webhook_service.py +422 -0
  291. core/api/services/workspace_sync.py +164 -0
  292. core/api/templates/__init__.py +1 -0
  293. core/api/templates/markdown_share.py +164 -0
  294. core/api/terminal.py +1031 -0
  295. core/api/tests/__init__.py +0 -0
  296. core/api/tests/test_agent_facing_auth_dependencies.py +132 -0
  297. core/api/tests/test_audit_permissions.py +133 -0
  298. core/api/tests/test_backfill_session_conversations.py +90 -0
  299. core/api/tests/test_backfill_working_seconds_msg.py +129 -0
  300. core/api/tests/test_claude_metrics.py +326 -0
  301. core/api/tests/test_codex_metrics.py +189 -0
  302. core/api/tests/test_finder_paths.py +74 -0
  303. core/api/tests/test_git_ops_merge.py +155 -0
  304. core/api/tests/test_learnings_check_search.py +81 -0
  305. core/api/tests/test_metrics_providers.py +133 -0
  306. core/api/tests/test_migration_087.py +164 -0
  307. core/api/tests/test_migration_088.py +94 -0
  308. core/api/tests/test_migration_089.py +116 -0
  309. core/api/tests/test_openai_responses.py +24 -0
  310. core/api/tests/test_opencode_metrics.py +740 -0
  311. core/api/tests/test_opencode_sessions.py +321 -0
  312. core/api/tests/test_pr_workflow_e2e.py +457 -0
  313. core/api/tests/test_projects_handoffs.py +31 -0
  314. core/api/tests/test_providers.py +138 -0
  315. core/api/tests/test_safety_bridge.py +347 -0
  316. core/api/tests/test_session_catalog.py +142 -0
  317. core/api/tests/test_session_conversations.py +512 -0
  318. core/api/tests/test_session_metrics_service.py +270 -0
  319. core/api/tests/test_session_resume_paths.py +548 -0
  320. core/api/tests/test_session_theme_mode_migration.py +56 -0
  321. core/api/tests/test_sessions_rbac.py +131 -0
  322. core/api/tests/test_share_edit.py +398 -0
  323. core/api/tests/test_share_repo.py +200 -0
  324. core/api/tests/test_terminal_session_manager.py +98 -0
  325. core/api/tests/test_terminal_upload.py +34 -0
  326. core/api/tests/test_tmux.py +272 -0
  327. core/api/tests/test_workspace_sync.py +186 -0
  328. core/api/tests/test_ws_ticket_in_memory.py +73 -0
  329. core/api/use_cases/__init__.py +11 -0
  330. core/api/use_cases/_context.py +89 -0
  331. core/api/use_cases/_errors.py +62 -0
  332. core/api/use_cases/_roles.py +16 -0
  333. core/api/use_cases/audit.py +171 -0
  334. core/api/use_cases/brain.py +1232 -0
  335. core/api/use_cases/costs.py +249 -0
  336. core/api/use_cases/graph.py +1153 -0
  337. core/api/use_cases/handoffs.py +506 -0
  338. core/api/use_cases/ingest_triage.py +1229 -0
  339. core/api/use_cases/learnings.py +538 -0
  340. core/api/use_cases/projects.py +705 -0
  341. core/api/use_cases/pull_requests.py +415 -0
  342. core/api/use_cases/search.py +926 -0
  343. core/api/use_cases/tasks.py +1495 -0
  344. core/api/visibility.py +141 -0
  345. core/cli/__init__.py +5 -0
  346. core/cli/_index_source.py +632 -0
  347. core/cli/_runtime_ctx.py +160 -0
  348. core/cli/_transmute.py +241 -0
  349. core/cli/marvis_doctor.py +704 -0
  350. core/cli/marvis_feedback.py +396 -0
  351. core/cli/marvis_governance.py +315 -0
  352. core/cli/marvis_hooks.py +515 -0
  353. core/cli/marvis_init.py +757 -0
  354. core/cli/marvis_mcp.py +401 -0
  355. core/cli/marvis_runtime.py +855 -0
  356. core/cli/marvis_telemetry.py +228 -0
  357. core/scripts/_drift_check.py +716 -0
  358. core/scripts/_frontmatter.py +66 -0
  359. core/scripts/_graph_writer.py +189 -0
  360. core/scripts/ast_parser.py +1553 -0
  361. core/scripts/install_hooks/__init__.py +1 -0
  362. core/scripts/install_hooks/_config.sh +109 -0
  363. core/scripts/install_hooks/block-dangerous-bash.sh +23 -0
  364. core/scripts/install_hooks/block-db-direct-write.sh +23 -0
  365. core/scripts/install_hooks/block-push-no-task.sh +23 -0
  366. core/scripts/install_hooks/block-staging-to-prod.sh +23 -0
  367. core/scripts/install_hooks/block-subtree-push.sh +23 -0
  368. core/scripts/install_hooks/config.json +53 -0
  369. core/scripts/install_hooks/enforce-no-merge-main.sh +23 -0
  370. core/scripts/install_hooks/enforce-worktree.sh +23 -0
  371. core/scripts/install_hooks/quality-gate.sh +170 -0
  372. core/scripts/install_hooks/safety_bridge.py +968 -0
  373. core/scripts/install_hooks/secret-scan.sh +23 -0
  374. core/scripts/migrate_spike_node_ids.py +122 -0
  375. core/scripts/populate_artifacts.py +2198 -0
  376. core/scripts/populate_cross_project.py +2457 -0
  377. core/scripts/populate_inbox_nodes.py +357 -0
  378. core/scripts/populate_pr_impact.py +267 -0
  379. core/scripts/populate_project_nodes.py +603 -0
  380. core/scripts/populate_touch_counter.py +337 -0
  381. core/scripts/reparse_failed.py +57 -0
  382. core/scripts/safety_bridge.py +968 -0
  383. core/telemetry/__init__.py +9 -0
  384. core/telemetry/client.py +405 -0
  385. core/telemetry/schema.py +122 -0
  386. core/wizard/__init__.py +65 -0
  387. core/wizard/byok_vault.py +147 -0
  388. core/wizard/defaults.py +58 -0
  389. core/wizard/state.py +117 -0
  390. core/wizard/steps.py +70 -0
  391. core/wizard/validation.py +136 -0
  392. marvisx_cli-0.1.0.dist-info/METADATA +201 -0
  393. marvisx_cli-0.1.0.dist-info/RECORD +587 -0
  394. marvisx_cli-0.1.0.dist-info/WHEEL +5 -0
  395. marvisx_cli-0.1.0.dist-info/entry_points.txt +3 -0
  396. marvisx_cli-0.1.0.dist-info/licenses/LICENSE +98 -0
  397. marvisx_cli-0.1.0.dist-info/top_level.txt +3 -0
  398. migrations/001_initial.sql +33 -0
  399. migrations/002_tasks.sql +30 -0
  400. migrations/003_session_management.sql +7 -0
  401. migrations/004_projects_comments.sql +65 -0
  402. migrations/005_session_intelligence.sql +15 -0
  403. migrations/006_settings.sql +12 -0
  404. migrations/007_task_scoring.sql +12 -0
  405. migrations/008_cost_tracking.sql +31 -0
  406. migrations/009_session_card_metrics.sql +3 -0
  407. migrations/010_monitoring.sql +55 -0
  408. migrations/012_agent_api.sql +21 -0
  409. migrations/013_session_complete.sql +8 -0
  410. migrations/015_pull_requests.sql +43 -0
  411. migrations/015_pull_requests_down.sql +5 -0
  412. migrations/016_users_raci.sql +116 -0
  413. migrations/017_task_cost_entries.sql +87 -0
  414. migrations/018_agents.sql +73 -0
  415. migrations/018_agents_down.sql +13 -0
  416. migrations/019_review_feedback.sql +18 -0
  417. migrations/020_pr_commit_sha.sql +4 -0
  418. migrations/021_webhook_events.sql +18 -0
  419. migrations/022_devx_agent_managed.sql +11 -0
  420. migrations/022_devx_agent_managed_down.sql +6 -0
  421. migrations/023_devx_p1_gate.sql +7 -0
  422. migrations/023_devx_p1_gate_down.sql +3 -0
  423. migrations/024_chat_messages.sql +16 -0
  424. migrations/024_pr_conversation_id.sql +8 -0
  425. migrations/024_task_indexes.sql +21 -0
  426. migrations/024_task_indexes_down.sql +7 -0
  427. migrations/025_audit_log.sql +17 -0
  428. migrations/026_agent_tokens.sql +20 -0
  429. migrations/027_teams_auth_phase_b.sql +35 -0
  430. migrations/028_learnings.sql +23 -0
  431. migrations/029_team_roles.sql +14 -0
  432. migrations/030_finder_pins.sql +10 -0
  433. migrations/031_pr_deploy_status.sql +9 -0
  434. migrations/032_task_reminders.sql +7 -0
  435. migrations/033_events_retry_count.sql +6 -0
  436. migrations/033_session_owner.sql +9 -0
  437. migrations/034_notifications.sql +38 -0
  438. migrations/035_shared_links.sql +15 -0
  439. migrations/036_session_index_upgrade.sql +29 -0
  440. migrations/037_pr_approval.sql +15 -0
  441. migrations/038_pr_submitted_by.sql +6 -0
  442. migrations/039_push_subscriptions.sql +17 -0
  443. migrations/040_semantic_search.sql +16 -0
  444. migrations/041_workspaces.sql +63 -0
  445. migrations/042_oidc_providers.sql +24 -0
  446. migrations/043_ci_checks.sql +31 -0
  447. migrations/044_agent_metrics.sql +30 -0
  448. migrations/045_documents_doc_type.sql +5 -0
  449. migrations/046_salience.sql +13 -0
  450. migrations/047_seed_missing_agents.sql +6 -0
  451. migrations/048_fix_agent_paths_roles.sql +5 -0
  452. migrations/049_agent_role_and_learnings_schema.sql +3 -0
  453. migrations/050_session_provider.sql +2 -0
  454. migrations/051_session_launch_profile.sql +4 -0
  455. migrations/052_session_theme_mode.sql +2 -0
  456. migrations/052_task_kind.sql +4 -0
  457. migrations/053_inbox_items.sql +31 -0
  458. migrations/054_inbox_triage_contract.sql +30 -0
  459. migrations/055_inbox_topic_treatment.sql +12 -0
  460. migrations/056_inbox_treatment_read_save.sql +57 -0
  461. migrations/057_session_theme_mode_backfill.sql +4 -0
  462. migrations/058_inbox_item_status_lifecycle.sql +13 -0
  463. migrations/059_inbox_tldr_and_source_scores.sql +18 -0
  464. migrations/060_newsletter.sql +16 -0
  465. migrations/061_inbox_redesign.sql +69 -0
  466. migrations/062_fix_inbox_sources_backfill.sql +37 -0
  467. migrations/063_task_completion_mode.sql +23 -0
  468. migrations/064_judge_mode_setting.sql +4 -0
  469. migrations/065_knowledge_graph_spike.sql +40 -0
  470. migrations/066_digest_ranking_inputs.sql +10 -0
  471. migrations/066_kg_artifact_nodes.sql +129 -0
  472. migrations/067_inbox_digest_selections.sql +28 -0
  473. migrations/067_kg_temporal.sql +53 -0
  474. migrations/068_inbox_digest_app_settings.sql +9 -0
  475. migrations/068_kg_touch_counter.sql +52 -0
  476. migrations/069_kg_doc_types.sql +117 -0
  477. migrations/070_digest_ranking_inputs_recovery.sql +3 -0
  478. migrations/071_inbox_digest_selections_recovery.sql +3 -0
  479. migrations/072_inbox_digest_app_settings_recovery.sql +3 -0
  480. migrations/073_kg_cross_project.sql +216 -0
  481. migrations/073_kg_cross_project_down.sql +77 -0
  482. migrations/074_kg_infra_types.sql +208 -0
  483. migrations/074_kg_infra_types_down.sql +80 -0
  484. migrations/075_kg_file_state_recovery.sql +35 -0
  485. migrations/075_kg_file_state_recovery_down.sql +5 -0
  486. migrations/076_kg_watcher_state.sql +33 -0
  487. migrations/076_kg_watcher_state_down.sql +3 -0
  488. migrations/077_kg_doc_types_extend.sql +226 -0
  489. migrations/077_kg_doc_types_extend_down.sql +80 -0
  490. migrations/078_kg_fts5.sql +102 -0
  491. migrations/078_kg_fts5_down.sql +14 -0
  492. migrations/079_kg_missing_indexes.sql +31 -0
  493. migrations/079_kg_missing_indexes_down.sql +10 -0
  494. migrations/080_kg_fts5_extended.sql +232 -0
  495. migrations/080_kg_fts5_extended_down.sql +25 -0
  496. migrations/081_kg_lens_indexes.sql +9 -0
  497. migrations/081_kg_lens_indexes_down.sql +3 -0
  498. migrations/082_kg_pins.sql +26 -0
  499. migrations/082_kg_pins_down.sql +14 -0
  500. migrations/083_kg_graph_nodes_degree.sql +20 -0
  501. migrations/083_kg_graph_nodes_degree_down.sql +15 -0
  502. migrations/084_drop_legacy_scheduler_tables.sql +58 -0
  503. migrations/084_drop_legacy_scheduler_tables_down.sql +112 -0
  504. migrations/085_kg_edge_resolves_to.sql +142 -0
  505. migrations/085_kg_edge_resolves_to_down.sql +66 -0
  506. migrations/086_project_status_updates_feed.sql +20 -0
  507. migrations/086_project_status_updates_feed_down.sql +36 -0
  508. migrations/087_session_metrics_dual.sql +50 -0
  509. migrations/087_session_metrics_dual_down.sql +21 -0
  510. migrations/088_rename_context_pct_legacy.sql +23 -0
  511. migrations/088_rename_context_pct_legacy_down.sql +8 -0
  512. migrations/089_session_metrics_equivalent_cost.sql +26 -0
  513. migrations/089_session_metrics_equivalent_cost_down.sql +11 -0
  514. migrations/090_kg_inbox_node_type.sql +26 -0
  515. migrations/090_kg_inbox_node_type_down.sql +20 -0
  516. migrations/091_kg_inbox_node_type_check.sql +265 -0
  517. migrations/091_kg_inbox_node_type_check_down.sql +129 -0
  518. migrations/092_sessions_activity_state_ts.sql +29 -0
  519. migrations/092_sessions_activity_state_ts_down.sql +14 -0
  520. migrations/093_sessions_activity_state_column.sql +29 -0
  521. migrations/093_sessions_activity_state_column_down.sql +10 -0
  522. migrations/094_ingest_pending.sql +55 -0
  523. migrations/094_ingest_pending_down.sql +15 -0
  524. migrations/095_kg_intent_first.sql +77 -0
  525. migrations/095_kg_intent_first_down.sql +25 -0
  526. migrations/096_kg_xlsx_artifact_prefix.sql +17 -0
  527. migrations/096_kg_xlsx_artifact_prefix_down.sql +11 -0
  528. migrations/097_ingest_change_history.sql +37 -0
  529. migrations/097_ingest_change_history_down.sql +13 -0
  530. migrations/098_kg_node_type_business.sql +254 -0
  531. migrations/098_kg_node_type_business_down.sql +195 -0
  532. migrations/099_kg_edges_restore_weight.sql +58 -0
  533. migrations/099_kg_edges_restore_weight_down.sql +12 -0
  534. migrations/100_kg_enriched_at.sql +25 -0
  535. migrations/100_kg_enriched_at_down.sql +12 -0
  536. migrations/101_local_llm_shadow_comparisons.sql +66 -0
  537. migrations/101_local_llm_shadow_comparisons_down.sql +15 -0
  538. migrations/102_promote_llm_costs.sql +69 -0
  539. migrations/102_promote_llm_costs_down.sql +19 -0
  540. migrations/103_ingest_skipped_log.sql +46 -0
  541. migrations/103_ingest_skipped_log_down.sql +15 -0
  542. migrations/120_docs_governance.sql +50 -0
  543. migrations/120_docs_governance_down.sql +11 -0
  544. migrations/121_notification_event_fk_cleanup.sql +21 -0
  545. migrations/121_notification_event_fk_cleanup_down.sql +10 -0
  546. migrations/122_docs_drift_history.sql +34 -0
  547. migrations/122_docs_drift_history_down.sql +15 -0
  548. migrations/123_ingest_parser_waiting_status.sql +69 -0
  549. migrations/123_ingest_parser_waiting_status_down.sql +69 -0
  550. migrations/124_heypocket_recordings.sql +63 -0
  551. migrations/124_heypocket_recordings_down.sql +13 -0
  552. migrations/125_kg_node_type_record.sql +219 -0
  553. migrations/125_kg_node_type_record_down.sql +205 -0
  554. migrations/126_ingest_terminal_upload_source_kind.sql +69 -0
  555. migrations/126_ingest_terminal_upload_source_kind_down.sql +69 -0
  556. migrations/127_brain_v1_substrate.sql +200 -0
  557. migrations/127_brain_v1_substrate_down.sql +32 -0
  558. migrations/128_brain_drift_signals.sql +157 -0
  559. migrations/128_brain_drift_signals_down.sql +23 -0
  560. migrations/129_brain_memory_operations.sql +232 -0
  561. migrations/129_brain_memory_operations_down.sql +27 -0
  562. migrations/130_brain_findings.sql +258 -0
  563. migrations/130_brain_findings_down.sql +29 -0
  564. migrations/132_kg_pr_modifies.sql +242 -0
  565. migrations/132_kg_pr_modifies_down.sql +99 -0
  566. migrations/133_brain_v1_2_direction_schema.sql +476 -0
  567. migrations/133_brain_v1_2_direction_schema_down.sql +273 -0
  568. migrations/134_brain_journal_narrative_polished.sql +8 -0
  569. migrations/134_brain_journal_narrative_polished_down.sql +6 -0
  570. migrations/135_kg_edges_provider.sql +21 -0
  571. migrations/136_documents_fts.sql +56 -0
  572. migrations/137_promote_llm_costs.sql +59 -0
  573. migrations/137_promote_llm_costs_down.sql +19 -0
  574. migrations/138_ingest_api_keys.sql +39 -0
  575. migrations/138_ingest_api_keys_down.sql +8 -0
  576. migrations/139_ingest_pending_ingress.sql +91 -0
  577. migrations/139_ingest_pending_ingress_down.sql +73 -0
  578. migrations/140_ingest_idempotency_quota.sql +45 -0
  579. migrations/140_ingest_idempotency_quota_down.sql +9 -0
  580. migrations/141_ingest_pending_metadata.sql +16 -0
  581. migrations/141_ingest_pending_metadata_down.sql +7 -0
  582. migrations/142_llm_function_config.sql +36 -0
  583. migrations/142_llm_function_config_down.sql +8 -0
  584. migrations/143_kg_code_embeddings.sql +25 -0
  585. migrations/143_kg_code_embeddings_down.sql +5 -0
  586. migrations/__init__.py +4 -0
  587. projects/_template/project.yaml +46 -0
@@ -0,0 +1,388 @@
1
+ # Brain v1 — CE3 M8 Cascade Rollup builder (sub-03 §11.5).
2
+ #
3
+ # Deterministic aggregation: child Journal entries within a scope window →
4
+ # proposed append-only patch to parent context.md.
5
+ # Disabled by default (`brain_memory_ops_cascade_enabled`).
6
+ #
7
+ # Invariants (sub-03 §11.5 #8-#12):
8
+ # * Disabled by default. v1.03 baseline behavior unchanged.
9
+ # * Empty window → no operation. (#9)
10
+ # * Cool-down respected. (#10)
11
+ # * Byte-stable bullet ordering: sort by event_type tag then recency DESC. (#11)
12
+ # * Cross-scope leak: project A entries NEVER appear in project B rollup. (#12)
13
+ # * Apply guidance-only: response carries target_path + body suggestion, NEVER writes.
14
+ # * NO LLM polish. Aggregate is mechanical (count + tag + recency).
15
+ # * Append-only patch: NEVER rewrite preamble.
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ from collections import defaultdict
20
+ from dataclasses import dataclass
21
+ from datetime import datetime, timedelta, timezone
22
+ from typing import Any
23
+
24
+ import aiosqlite
25
+
26
+ from core.api.db import acquire_db
27
+ from core.api.models.brain import ProposedWriteContextMdAppend
28
+ from core.api.services.brain.compound_bridge import (
29
+ build_proposed_write_context_md_append,
30
+ )
31
+ from core.api.services.brain.memory_ops import (
32
+ DigestEventRow,
33
+ JournalEntryRow,
34
+ OperationDraft,
35
+ OpSnapshot,
36
+ evidence_hash,
37
+ make_operation_id,
38
+ )
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ @dataclass(slots=True)
44
+ class CascadeSettings:
45
+ enabled: bool = False
46
+ window_days: int = 14
47
+ min_interval_days: int = 7
48
+ max_bullets_per_group: int = 3
49
+ program_scope_enabled: bool = False
50
+
51
+
52
+ _EVENT_TYPE_PRETTY: dict[str, str] = {
53
+ "decision": "Decision",
54
+ "procedure": "Procedure",
55
+ "open_loop": "Open loop",
56
+ "regression_signal": "Regression",
57
+ "external_update": "External update",
58
+ "new_artifact": "Artifact",
59
+ }
60
+
61
+
62
+ async def load_cascade_settings(db: aiosqlite.Connection) -> CascadeSettings:
63
+ """Load cascade settings from app_settings. Defaults match sub-03 §11.5."""
64
+ rows = await (
65
+ await db.execute(
66
+ "SELECT key, value FROM app_settings"
67
+ " WHERE key IN ("
68
+ " 'brain_memory_ops_cascade_enabled',"
69
+ " 'brain_memory_ops_cascade_window_days',"
70
+ " 'brain_memory_ops_cascade_min_interval_days',"
71
+ " 'brain_memory_ops_cascade_max_bullets_per_group',"
72
+ " 'brain_memory_ops_cascade_program_scope_enabled'"
73
+ ")"
74
+ )
75
+ ).fetchall()
76
+ cfg = {r[0] if not hasattr(r, "keys") else r["key"]: (r[1] if not hasattr(r, "keys") else r["value"]) for r in rows}
77
+
78
+ def _as_int(key: str, default: int) -> int:
79
+ raw = cfg.get(key)
80
+ try:
81
+ return int(raw) if raw is not None else default
82
+ except (TypeError, ValueError):
83
+ return default
84
+
85
+ def _as_bool(key: str, default: bool) -> bool:
86
+ raw = cfg.get(key)
87
+ if raw is None:
88
+ return default
89
+ return str(raw).strip().lower() in {"1", "true", "yes", "on"}
90
+
91
+ return CascadeSettings(
92
+ enabled=_as_bool("brain_memory_ops_cascade_enabled", False),
93
+ window_days=_as_int("brain_memory_ops_cascade_window_days", 14),
94
+ min_interval_days=_as_int("brain_memory_ops_cascade_min_interval_days", 7),
95
+ max_bullets_per_group=_as_int(
96
+ "brain_memory_ops_cascade_max_bullets_per_group", 3
97
+ ),
98
+ program_scope_enabled=_as_bool(
99
+ "brain_memory_ops_cascade_program_scope_enabled", False
100
+ ),
101
+ )
102
+
103
+
104
+ def _classify_entry(body: dict[str, Any]) -> tuple[str, str]:
105
+ """Map a child journal entry to (tag, title). Deterministic — picks the
106
+ first non-empty section. Order matters (decision > procedure > open_loop
107
+ > regression > external > new_artifact)."""
108
+ if body.get("decisions_observed"):
109
+ items = body["decisions_observed"]
110
+ title = str(items[0]) if items else ""
111
+ return ("decision", title[:120])
112
+ if isinstance(body.get("notable_context"), list) and any(
113
+ isinstance(x, dict) and x.get("kind") == "procedure" for x in body["notable_context"]
114
+ ):
115
+ for x in body["notable_context"]:
116
+ if isinstance(x, dict) and x.get("kind") == "procedure":
117
+ return ("procedure", str(x.get("title", ""))[:120])
118
+ if body.get("open_loops"):
119
+ items = body["open_loops"]
120
+ first = items[0] if items else None
121
+ if isinstance(first, dict):
122
+ return ("open_loop", str(first.get("title", ""))[:120])
123
+ return ("open_loop", str(first or "")[:120])
124
+ if isinstance(body.get("notable_context"), list):
125
+ for x in body["notable_context"]:
126
+ if isinstance(x, dict) and x.get("kind") == "regression":
127
+ return ("regression_signal", str(x.get("title", ""))[:120])
128
+ if isinstance(x, dict) and x.get("kind") == "external_update":
129
+ return ("external_update", str(x.get("title", ""))[:120])
130
+ if body.get("what_changed"):
131
+ items = body["what_changed"]
132
+ first = items[0] if items else None
133
+ if isinstance(first, dict):
134
+ return ("new_artifact", str(first.get("title", ""))[:120])
135
+ return ("new_artifact", str(first or "")[:120])
136
+ return ("new_artifact", "")
137
+
138
+
139
+ @dataclass(slots=True, frozen=True)
140
+ class _ScopedEntry:
141
+ entry_id: str
142
+ cycle_key: str
143
+ scope_type: str
144
+ scope_key: str
145
+ tag: str
146
+ title: str
147
+ published_at: datetime
148
+
149
+
150
+ def _group_entries(
151
+ entries: list[JournalEntryRow],
152
+ ) -> dict[str, list[_ScopedEntry]]:
153
+ """Group classified entries by tag. Cross-scope safety enforced by caller —
154
+ this fn receives entries already filtered to a single parent scope."""
155
+ grouped: dict[str, list[_ScopedEntry]] = defaultdict(list)
156
+ for e in entries:
157
+ tag, title = _classify_entry(e.body)
158
+ grouped[tag].append(
159
+ _ScopedEntry(
160
+ entry_id=e.entry_id,
161
+ cycle_key=e.cycle_key,
162
+ scope_type=e.scope_type,
163
+ scope_key=e.scope_key,
164
+ tag=tag,
165
+ title=title,
166
+ published_at=e.published_at,
167
+ )
168
+ )
169
+ return grouped
170
+
171
+
172
+ def compose_bullets(
173
+ grouped: dict[str, list[_ScopedEntry]],
174
+ *,
175
+ max_bullets_per_group: int,
176
+ ) -> list[str]:
177
+ """Byte-stable rendering of bullet groups (invariant #11).
178
+
179
+ Sort:
180
+ - Groups: alphabetical by tag (deterministic).
181
+ - Within group: by published_at DESC, ties broken by entry_id ASC.
182
+ """
183
+ bullets: list[str] = []
184
+ for tag in sorted(grouped.keys()):
185
+ members = grouped[tag]
186
+ if not members:
187
+ continue
188
+ ordered = sorted(
189
+ members,
190
+ key=lambda x: (-x.published_at.timestamp(), x.entry_id),
191
+ )
192
+ titles = [m.title for m in ordered[:max_bullets_per_group] if m.title]
193
+ if not titles:
194
+ continue
195
+ pretty = _EVENT_TYPE_PRETTY.get(tag, tag.replace("_", " ").title())
196
+ bullets.append(f"- {len(members)} {pretty}: {', '.join(titles)}")
197
+ return bullets
198
+
199
+
200
+ def compose_body(
201
+ *,
202
+ cycle_key: str,
203
+ bullets: list[str],
204
+ ) -> str:
205
+ """Append-only context.md patch body (never rewrites preamble)."""
206
+ if not bullets:
207
+ return ""
208
+ header = f"## Brain rollup ({cycle_key})"
209
+ return f"{header}\n" + "\n".join(bullets)
210
+
211
+
212
+ def context_md_path_for_project(scope_key: str) -> str:
213
+ """Deterministic context.md path. /data/projects/<slug>/context.md.
214
+
215
+ Project-only in v1 — program/company scopes off until pilot validated.
216
+ """
217
+ return f"/data/projects/{scope_key}/context.md"
218
+
219
+
220
+ async def _last_cascade_at_for_scope(
221
+ db: aiosqlite.Connection,
222
+ *,
223
+ scope_type: str,
224
+ scope_key: str,
225
+ ) -> datetime | None:
226
+ row = await (
227
+ await db.execute(
228
+ "SELECT MAX(detected_at) FROM brain_memory_operations"
229
+ " WHERE operation_type = 'cascade_rollup'"
230
+ " AND scope_type = ? AND scope_key = ?",
231
+ (scope_type, scope_key),
232
+ )
233
+ ).fetchone()
234
+ if row is None:
235
+ return None
236
+ raw = row[0] if not hasattr(row, "keys") else row[0]
237
+ if not raw:
238
+ return None
239
+ try:
240
+ dt = datetime.fromisoformat(str(raw).replace("Z", "+00:00"))
241
+ except ValueError:
242
+ return None
243
+ if dt.tzinfo is None:
244
+ dt = dt.replace(tzinfo=timezone.utc)
245
+ return dt
246
+
247
+
248
+ async def build_cascade_drafts(
249
+ *,
250
+ snapshot: OpSnapshot,
251
+ now: datetime,
252
+ ) -> list[OperationDraft]:
253
+ """Build CE3 cascade_rollup proposals for each project scope present in
254
+ the current snapshot. Disabled by default.
255
+
256
+ Children: prior journal entries for the project within `window_days`
257
+ (read via secondary DB query — outside the snapshot to capture history).
258
+ Cool-down: skip if prior cascade rollup detected_at < min_interval_days ago.
259
+ """
260
+ async with acquire_db() as db:
261
+ db.row_factory = aiosqlite.Row
262
+ cfg = await load_cascade_settings(db)
263
+ if not cfg.enabled:
264
+ return []
265
+
266
+ # Identify project scopes that received a child journal entry this cycle.
267
+ project_scopes: set[str] = set()
268
+ for entry in snapshot.journal_entries:
269
+ if entry.scope_type == "project":
270
+ project_scopes.add(entry.scope_key)
271
+
272
+ if not project_scopes:
273
+ return []
274
+
275
+ drafts: list[OperationDraft] = []
276
+ cycle_date_str = snapshot.cycle_key
277
+ try:
278
+ ck_date = datetime.fromisoformat(cycle_date_str).date()
279
+ except ValueError:
280
+ return []
281
+ since = (ck_date - timedelta(days=cfg.window_days)).isoformat()
282
+
283
+ for scope_key in sorted(project_scopes):
284
+ # Cool-down (invariant #10).
285
+ last_at = await _last_cascade_at_for_scope(
286
+ db, scope_type="project", scope_key=scope_key
287
+ )
288
+ if last_at is not None:
289
+ age = (now - last_at).days
290
+ if age < cfg.min_interval_days:
291
+ continue
292
+
293
+ # Pull child journal entries for THIS project within the window.
294
+ rows = await (
295
+ await db.execute(
296
+ "SELECT entry_id, cycle_key, scope_type, scope_key, program_key,"
297
+ " body_json, is_empty, published_at"
298
+ " FROM brain_journal_entries j"
299
+ " JOIN brain_runs r ON r.run_id = j.run_id"
300
+ " WHERE j.workspace_id = ?"
301
+ " AND j.scope_type = 'project' AND j.scope_key = ?"
302
+ " AND j.cycle_key >= ? AND j.cycle_key <= ?"
303
+ " AND j.is_empty = 0"
304
+ " AND r.status IN ('succeeded','partial')"
305
+ " AND r.superseded_by_run_id IS NULL"
306
+ " ORDER BY j.cycle_key DESC, j.published_at DESC",
307
+ (
308
+ snapshot.workspace_id,
309
+ scope_key,
310
+ since,
311
+ snapshot.cycle_key,
312
+ ),
313
+ )
314
+ ).fetchall()
315
+ if not rows:
316
+ continue
317
+ entries: list[JournalEntryRow] = []
318
+ import json as _json
319
+
320
+ for r in rows:
321
+ pub = r["published_at"] if hasattr(r, "keys") else r[7]
322
+ pub_dt = datetime.fromisoformat(str(pub).replace("Z", "+00:00"))
323
+ if pub_dt.tzinfo is None:
324
+ pub_dt = pub_dt.replace(tzinfo=timezone.utc)
325
+ body_raw = r["body_json"] if hasattr(r, "keys") else r[5]
326
+ try:
327
+ body = _json.loads(body_raw or "{}")
328
+ except (TypeError, ValueError):
329
+ body = {}
330
+ entries.append(
331
+ JournalEntryRow(
332
+ entry_id=r["entry_id"] if hasattr(r, "keys") else r[0],
333
+ cycle_key=r["cycle_key"] if hasattr(r, "keys") else r[1],
334
+ scope_type=r["scope_type"] if hasattr(r, "keys") else r[2],
335
+ scope_key=r["scope_key"] if hasattr(r, "keys") else r[3],
336
+ program_key=r["program_key"] if hasattr(r, "keys") else r[4],
337
+ body=body if isinstance(body, dict) else {},
338
+ is_empty=False,
339
+ published_at=pub_dt,
340
+ )
341
+ )
342
+
343
+ grouped = _group_entries(entries)
344
+ bullets = compose_bullets(
345
+ grouped, max_bullets_per_group=cfg.max_bullets_per_group
346
+ )
347
+ if not bullets:
348
+ continue
349
+ body = compose_body(cycle_key=snapshot.cycle_key, bullets=bullets)
350
+ path = context_md_path_for_project(scope_key)
351
+ child_ids = sorted(set(e.entry_id for e in entries))
352
+
353
+ payload = build_proposed_write_context_md_append(
354
+ path=path,
355
+ body=body,
356
+ rollup_cycle_key=snapshot.cycle_key,
357
+ child_entry_ids=child_ids,
358
+ )
359
+ evidence = [f"journal:{eid}" for eid in child_ids]
360
+ drafts.append(
361
+ OperationDraft(
362
+ operation_type="cascade_rollup",
363
+ scope_type="project",
364
+ scope_key=scope_key,
365
+ program_key=None,
366
+ source_ref=path,
367
+ target_ref="",
368
+ evidence=evidence,
369
+ summary=(
370
+ f"Cascade rollup proposal for project {scope_key}: "
371
+ f"{len(child_ids)} child journal entries in last "
372
+ f"{cfg.window_days} days."
373
+ ),
374
+ proposed_write=payload,
375
+ involved_projects=[scope_key],
376
+ )
377
+ )
378
+ return drafts
379
+
380
+
381
+ __all__ = [
382
+ "CascadeSettings",
383
+ "build_cascade_drafts",
384
+ "compose_body",
385
+ "compose_bullets",
386
+ "context_md_path_for_project",
387
+ "load_cascade_settings",
388
+ ]
@@ -0,0 +1,215 @@
1
+ # Brain v1 — Compound integration boundary (sub-03 §6 M2).
2
+ #
3
+ # This module is the SINGLE seam between L4 Memory Operations and the
4
+ # downstream Compound substrate (learning/guide/task/adr/doc_patch).
5
+ # Invariants:
6
+ # * No writes — only proposal construction + dry-run validation.
7
+ # * No raw SQL on substrate (tasks/learnings/handoffs).
8
+ # * No LLM imports — deterministic mapping only (parent §9.3).
9
+ # * Operator/agent applies via the apply guidance endpoint; that is the
10
+ # ONLY way an artifact actually gets created.
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+ from core.api.models.brain import (
16
+ OperationType,
17
+ ProposedWrite,
18
+ ProposedWriteADR,
19
+ ProposedWriteContextMdAppend,
20
+ ProposedWriteDocPatch,
21
+ ProposedWriteGuide,
22
+ ProposedWriteKGEdgeMetric,
23
+ ProposedWriteLearning,
24
+ ProposedWriteNone,
25
+ ProposedWriteTarget,
26
+ ProposedWriteTask,
27
+ )
28
+
29
+ # Static default mapping: operation_type → preferred proposed_write target_type.
30
+ # Builders can override (e.g. provenance_hardening → task vs doc_patch depending
31
+ # on evidence). This table is the canonical default used by M2/M5/M6.
32
+ DEFAULT_TARGET_FOR_OP: dict[OperationType, ProposedWriteTarget] = {
33
+ "reinforce": "kg_edge_metric",
34
+ "consolidate": "doc_patch",
35
+ "supersede_candidate": "doc_patch",
36
+ "provenance_hardening": "task",
37
+ "orphan_detected": "task",
38
+ "contradiction_detected": "task",
39
+ "cascade_rollup": "context_md_append",
40
+ "compression_candidate": "learning",
41
+ # Reserved literals (no producer in v1) — best-effort defaults:
42
+ "deduplicate": "doc_patch",
43
+ "promotion_candidate": "learning",
44
+ }
45
+
46
+
47
+ def build_proposed_write_task(
48
+ *,
49
+ operation_id: str,
50
+ title: str,
51
+ description: str,
52
+ project: str,
53
+ impact: int = 5,
54
+ confidence: int = 5,
55
+ ease: int = 5,
56
+ delegation: str = "hybrid",
57
+ priority: str = "medium",
58
+ extra_tags: list[str] | None = None,
59
+ ) -> ProposedWriteTask:
60
+ """M2 task proposal. Auto-tags with brain_op:{operation_id} for audit chain."""
61
+ tags = list(extra_tags or [])
62
+ audit_tag = f"brain_op:{operation_id}"
63
+ if audit_tag not in tags:
64
+ tags.append(audit_tag)
65
+ return ProposedWriteTask(
66
+ title=title,
67
+ description=description,
68
+ priority=priority, # type: ignore[arg-type]
69
+ project=project,
70
+ delegation=delegation, # type: ignore[arg-type]
71
+ impact=impact,
72
+ confidence=confidence,
73
+ ease=ease,
74
+ tags=sorted(set(tags)),
75
+ )
76
+
77
+
78
+ def build_proposed_write_learning(
79
+ *,
80
+ operation_id: str,
81
+ title: str,
82
+ category: str,
83
+ description: str,
84
+ prevention: str,
85
+ severity: str = "medium",
86
+ module: str | None = None,
87
+ project: str | None = None,
88
+ extra_tags: list[str] | None = None,
89
+ ) -> ProposedWriteLearning:
90
+ tags = list(extra_tags or [])
91
+ audit_tag = f"brain_op:{operation_id}"
92
+ if audit_tag not in tags:
93
+ tags.append(audit_tag)
94
+ return ProposedWriteLearning(
95
+ title=title,
96
+ category=category, # type: ignore[arg-type]
97
+ description=description,
98
+ prevention=prevention,
99
+ severity=severity, # type: ignore[arg-type]
100
+ module=module,
101
+ project=project,
102
+ tags=sorted(set(tags)),
103
+ )
104
+
105
+
106
+ def build_proposed_write_doc_patch(
107
+ *,
108
+ path: str,
109
+ unified_diff: str,
110
+ base_sha: str,
111
+ rationale: str,
112
+ ) -> ProposedWriteDocPatch:
113
+ return ProposedWriteDocPatch(
114
+ path=path,
115
+ unified_diff=unified_diff,
116
+ base_sha=base_sha,
117
+ rationale=rationale,
118
+ )
119
+
120
+
121
+ def build_proposed_write_kg_edge_metric(
122
+ *,
123
+ edge_id: str,
124
+ metric_kind: str = "reinforce_score",
125
+ delta: float = 0.0,
126
+ ) -> ProposedWriteKGEdgeMetric:
127
+ return ProposedWriteKGEdgeMetric(
128
+ edge_id=edge_id,
129
+ metric_kind=metric_kind, # type: ignore[arg-type]
130
+ delta=delta,
131
+ )
132
+
133
+
134
+ def build_proposed_write_context_md_append(
135
+ *,
136
+ path: str,
137
+ body: str,
138
+ rollup_cycle_key: str,
139
+ child_entry_ids: list[str],
140
+ ) -> ProposedWriteContextMdAppend:
141
+ return ProposedWriteContextMdAppend(
142
+ path=path,
143
+ body=body,
144
+ rollup_cycle_key=rollup_cycle_key,
145
+ child_entry_ids=list(child_entry_ids),
146
+ )
147
+
148
+
149
+ def build_proposed_write_guide(
150
+ *,
151
+ path: str,
152
+ title: str,
153
+ body: str,
154
+ source_refs: list[str],
155
+ ) -> ProposedWriteGuide:
156
+ return ProposedWriteGuide(
157
+ path=path,
158
+ title=title,
159
+ body=body,
160
+ source_refs=list(source_refs),
161
+ )
162
+
163
+
164
+ def build_proposed_write_adr(
165
+ *,
166
+ path: str,
167
+ title: str,
168
+ decision: str,
169
+ context: str,
170
+ consequences: str,
171
+ source_refs: list[str],
172
+ supersedes: list[str] | None = None,
173
+ ) -> ProposedWriteADR:
174
+ return ProposedWriteADR(
175
+ path=path,
176
+ title=title,
177
+ decision=decision,
178
+ context=context,
179
+ consequences=consequences,
180
+ source_refs=list(source_refs),
181
+ supersedes=list(supersedes or []),
182
+ )
183
+
184
+
185
+ def proposed_write_none() -> ProposedWriteNone:
186
+ return ProposedWriteNone()
187
+
188
+
189
+ def dry_run_proposed_write(
190
+ proposed: ProposedWrite,
191
+ ) -> tuple[bool, str | None]:
192
+ """Validate a discriminated-union ProposedWrite before INSERT.
193
+
194
+ Returns (ok, error_message). The Pydantic model already enforces field
195
+ constraints at construction — this hook is a future extensibility point
196
+ for cross-field checks (e.g. doc_patch base_sha resolvable). v1 returns
197
+ True for any successfully constructed instance.
198
+ """
199
+ if proposed is None:
200
+ return False, "proposed_write is None"
201
+ return True, None
202
+
203
+
204
+ __all__ = [
205
+ "DEFAULT_TARGET_FOR_OP",
206
+ "build_proposed_write_adr",
207
+ "build_proposed_write_context_md_append",
208
+ "build_proposed_write_doc_patch",
209
+ "build_proposed_write_guide",
210
+ "build_proposed_write_kg_edge_metric",
211
+ "build_proposed_write_learning",
212
+ "build_proposed_write_task",
213
+ "dry_run_proposed_write",
214
+ "proposed_write_none",
215
+ ]