brainiac-cli 0.20.48__tar.gz → 0.20.49__tar.gz

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 (368) hide show
  1. {brainiac_cli-0.20.48/src/brainiac_cli.egg-info → brainiac_cli-0.20.49}/PKG-INFO +4 -3
  2. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/pyproject.toml +20 -8
  3. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/AGENTS.md +1 -1
  4. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain_synthesis_codex.py +16 -3
  5. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_version.py +1 -1
  6. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/alerts.py +2 -2
  7. brainiac_cli-0.20.49/src/brain/atomic_temp.py +49 -0
  8. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/audit.py +7 -3
  9. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/audit_drift.py +2 -5
  10. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/autolink.py +12 -9
  11. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/backup.py +13 -2
  12. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/brief.py +21 -15
  13. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/chunk.py +24 -19
  14. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli.py +4 -1
  15. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/index_storage.py +59 -51
  16. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/lifecycle.py +1 -1
  17. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/owner_workflow.py +17 -14
  18. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_read_record.py +1 -1
  19. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/config.py +0 -7
  20. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/context.py +2 -7
  21. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/conventions_scan.py +20 -12
  22. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_briefing.py +19 -10
  23. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_capture.py +19 -17
  24. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_health.py +1 -1
  25. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_maintenance_state.py +2 -5
  26. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_shared.py +4 -1
  27. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_supersession_journal.py +16 -9
  28. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_materialize.py +11 -6
  29. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_sync.py +6 -3
  30. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor.py +3 -3
  31. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_checks.py +1 -1
  32. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_context.py +9 -2
  33. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_desktop.py +1 -1
  34. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_health.py +19 -12
  35. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_plugins.py +5 -0
  36. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_staging.py +3 -3
  37. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_vendor.py +17 -11
  38. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_vm.py +1 -1
  39. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_wiring.py +59 -36
  40. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/draft_drain.py +33 -2
  41. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/draft_update.py +3 -1
  42. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/egress.py +0 -11
  43. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed.py +4 -4
  44. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed_catalog.py +1 -1
  45. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed_onnx.py +2 -2
  46. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed_qwen.py +1 -1
  47. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/encryption.py +1 -1
  48. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_page.py +3 -8
  49. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_verify.py +2 -4
  50. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/context.py +3 -0
  51. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/deliverables.py +2 -1
  52. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/invariants.py +4 -1
  53. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/publish.py +3 -1
  54. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/frontmatter.py +1 -1
  55. brainiac_cli-0.20.49/src/brain/golden_executor.py +107 -0
  56. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/golden_ops.py +59 -26
  57. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/golden_probe_cases.py +15 -10
  58. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graph_ops.py +3 -1
  59. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/healthreport_sections.py +55 -40
  60. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_tools.py +4 -3
  61. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_zones.py +25 -17
  62. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/connection.py +5 -1
  63. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/base.py +76 -31
  64. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment.py +0 -9
  65. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_html.py +28 -19
  66. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_pdf.py +15 -8
  67. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/email.py +5 -0
  68. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/html.py +10 -2
  69. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/image.py +8 -1
  70. brainiac_cli-0.20.49/src/brain/ingest/handlers/ocr_retry.py +35 -0
  71. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/pdf.py +45 -6
  72. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/pptx.py +8 -1
  73. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/xlsx.py +19 -4
  74. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/zip.py +12 -1
  75. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_files.py +3 -7
  76. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_stages.py +16 -14
  77. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/tierguard.py +10 -4
  78. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init_seed.py +0 -50
  79. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_coverage.py +25 -15
  80. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_deliverables.py +4 -4
  81. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_reachability.py +12 -8
  82. brainiac_cli-0.20.49/src/brain/invariant_shared.py +32 -0
  83. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariants.py +6 -26
  84. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_folds.py +7 -7
  85. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_3.py +2 -5
  86. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_4.py +3 -1
  87. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_hotmd.py +18 -10
  88. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_notify.py +73 -50
  89. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_retention.py +38 -32
  90. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/notes.py +45 -40
  91. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay.py +38 -27
  92. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay_generated.py +6 -7
  93. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provision.py +1 -3
  94. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/querylog.py +4 -0
  95. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/querylog_digest.py +2 -2
  96. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/querylog_status.py +19 -10
  97. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/read_log.py +42 -33
  98. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/reexec.py +1 -1
  99. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation.py +3 -7
  100. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_exceptions_store.py +2 -4
  101. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_extract_retry.py +15 -10
  102. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_folds.py +5 -1
  103. brainiac_cli-0.20.49/src/brain/remediation_security.py +21 -0
  104. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_state.py +2 -4
  105. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/rerank.py +3 -3
  106. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/rerank_onnx.py +3 -3
  107. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/session_hook_settings.py +2 -4
  108. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/snapshot.py +5 -1
  109. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update.py +10 -2
  110. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_channels.py +44 -36
  111. brainiac_cli-0.20.49/src/brain/update_engine_only.py +48 -0
  112. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_ops.py +2 -1
  113. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_state.py +5 -4
  114. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_venv.py +3 -1
  115. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/vectors.py +3 -3
  116. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/vm_ceiling.py +2 -1
  117. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/vmstaging.py +0 -61
  118. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/workspaces.py +2 -4
  119. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49/src/brainiac_cli.egg-info}/PKG-INFO +4 -3
  120. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/SOURCES.txt +4 -0
  121. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/requires.txt +3 -2
  122. brainiac_cli-0.20.48/src/brain/atomic_temp.py +0 -6
  123. brainiac_cli-0.20.48/src/brain/invariant_shared.py +0 -4
  124. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/LICENSE +0 -0
  125. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/MANIFEST.in +0 -0
  126. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/README.md +0 -0
  127. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/setup.cfg +0 -0
  128. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/__init__.py +0 -0
  129. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/__main__.py +0 -0
  130. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/assets/graph-explorer-template.html +0 -0
  131. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/brand/brand-guide.md +0 -0
  132. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/keywords/glossary.md +0 -0
  133. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/people/roster.md +0 -0
  134. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/voice/voice-profile.md +0 -0
  135. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/routines/manifest.json +0 -0
  136. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-brief-mac.plist +0 -0
  137. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-brief.sh +0 -0
  138. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-synthesis-mac.plist +0 -0
  139. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-synthesis.sh +0 -0
  140. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brainiac-alerts.sh +0 -0
  141. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brainiac-egress-guard.sh +0 -0
  142. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/install-brief-mac.sh +0 -0
  143. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/install-brief-windows.ps1 +0 -0
  144. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/register_tasks.py +0 -0
  145. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/shorten_long_note_ids.py +0 -0
  146. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/vm-boundary-probe.sh +0 -0
  147. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/vm-selftest.sh +0 -0
  148. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/company.md +0 -0
  149. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/concept.md +0 -0
  150. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/daily.md +0 -0
  151. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/decision.md +0 -0
  152. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/deliverable.md +0 -0
  153. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/meeting.md +0 -0
  154. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/person.md +0 -0
  155. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/project.md +0 -0
  156. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/state-moc.md +0 -0
  157. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_optional.py +0 -0
  158. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/alerts_render.py +0 -0
  159. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/alerts_staging.py +0 -0
  160. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/anchor.py +0 -0
  161. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/attestation.py +0 -0
  162. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/audit_chain.py +0 -0
  163. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/brief_render.py +0 -0
  164. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/capture.py +0 -0
  165. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/classification.py +0 -0
  166. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/__init__.py +0 -0
  167. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/common_parser.py +0 -0
  168. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/connect.py +0 -0
  169. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/connect_parser.py +0 -0
  170. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/eval.py +0 -0
  171. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/eval_parser.py +0 -0
  172. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/index_storage_parser.py +0 -0
  173. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/ingest_storage.py +0 -0
  174. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/ingest_storage_parser.py +0 -0
  175. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/lifecycle_parser.py +0 -0
  176. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/maintenance.py +0 -0
  177. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/maintenance_parser.py +0 -0
  178. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/navigation.py +0 -0
  179. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/navigation_parser.py +0 -0
  180. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/owner_workflow_parser.py +0 -0
  181. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/presentation.py +0 -0
  182. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/presentation_parser.py +0 -0
  183. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/retrieval.py +0 -0
  184. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/retrieval_parser.py +0 -0
  185. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/update.py +0 -0
  186. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/update_parser.py +0 -0
  187. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/versioning.py +0 -0
  188. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/versioning_parser.py +0 -0
  189. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_help.py +0 -0
  190. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_render.py +0 -0
  191. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/config_hostpaths.py +0 -0
  192. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/connect.py +0 -0
  193. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/conventions.py +0 -0
  194. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/__init__.py +0 -0
  195. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_audit.py +0 -0
  196. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_capture_staging.py +0 -0
  197. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_durability.py +0 -0
  198. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_maintenance.py +0 -0
  199. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_retrieval.py +0 -0
  200. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_supersede_batch.py +0 -0
  201. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_supersession.py +0 -0
  202. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_leak_artifact.py +0 -0
  203. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_leak_databases.py +0 -0
  204. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_leak_notes.py +0 -0
  205. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_leak_scan.py +0 -0
  206. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_snapshot_guard.py +0 -0
  207. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_staging.py +0 -0
  208. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/dbretry.py +0 -0
  209. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_absorb.py +0 -0
  210. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_apply.py +0 -0
  211. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_ledger.py +0 -0
  212. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_previous.py +0 -0
  213. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_readme.py +0 -0
  214. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_shelf.py +0 -0
  215. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_mount_leak.py +0 -0
  216. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/egress_terms.py +0 -0
  217. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed_backends.py +0 -0
  218. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_cli.py +0 -0
  219. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_render.py +0 -0
  220. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_tray.py +0 -0
  221. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/__init__.py +0 -0
  222. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/daily.py +0 -0
  223. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/golden.py +0 -0
  224. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/graph.py +0 -0
  225. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/intake.py +0 -0
  226. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/orchestrator.py +0 -0
  227. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/organization.py +0 -0
  228. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/preflight.py +0 -0
  229. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/remediation.py +0 -0
  230. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/remediation_bookkeeping.py +0 -0
  231. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/reporting.py +0 -0
  232. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/retention.py +0 -0
  233. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/watchdogs.py +0 -0
  234. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/weekly.py +0 -0
  235. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/golden_probe.py +0 -0
  236. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graph.py +0 -0
  237. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graph_hygiene.py +0 -0
  238. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphify.py +0 -0
  239. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphify_edges.py +0 -0
  240. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphreport.py +0 -0
  241. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphreport_sections.py +0 -0
  242. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphreport_sections_2.py +0 -0
  243. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/guard_settings.py +0 -0
  244. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/healthreport.py +0 -0
  245. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/healthreport_unsigned.py +0 -0
  246. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/inbox.py +0 -0
  247. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/__init__.py +0 -0
  248. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_connection.py +0 -0
  249. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_families.py +0 -0
  250. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_graph.py +0 -0
  251. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_identity.py +0 -0
  252. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_lifecycle.py +0 -0
  253. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_models.py +0 -0
  254. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_planning.py +0 -0
  255. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_records.py +0 -0
  256. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_retirement.py +0 -0
  257. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_schema.py +0 -0
  258. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_search.py +0 -0
  259. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_settings.py +0 -0
  260. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_shared.py +0 -0
  261. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/__init__.py +0 -0
  262. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/diagnostics.py +0 -0
  263. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/duplicate_families.py +0 -0
  264. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/near_duplicates.py +0 -0
  265. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/rebuild.py +0 -0
  266. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/reranking.py +0 -0
  267. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/search.py +0 -0
  268. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/search_finalization.py +0 -0
  269. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/search_identity.py +0 -0
  270. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/search_ranking.py +0 -0
  271. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/sync.py +0 -0
  272. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/__init__.py +0 -0
  273. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/deliverables.py +0 -0
  274. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/__init__.py +0 -0
  275. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_gate.py +0 -0
  276. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_ledger.py +0 -0
  277. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_office.py +0 -0
  278. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/docx.py +0 -0
  279. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/ole.py +0 -0
  280. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/tables.py +0 -0
  281. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/text.py +0 -0
  282. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline.py +0 -0
  283. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_duplicates.py +0 -0
  284. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_injection.py +0 -0
  285. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_nested.py +0 -0
  286. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_run.py +0 -0
  287. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/tierguard_verdict.py +0 -0
  288. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/transcript.py +0 -0
  289. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init.py +0 -0
  290. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init_import.py +0 -0
  291. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init_samples.py +0 -0
  292. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init_steps.py +0 -0
  293. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/injection_fold.py +0 -0
  294. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/injection_scan.py +0 -0
  295. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/interview.py +0 -0
  296. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/interview_apply.py +0 -0
  297. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/interview_detect.py +0 -0
  298. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_crosstier.py +0 -0
  299. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_floors.py +0 -0
  300. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_ingest_guard.py +0 -0
  301. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_lane_health.py +0 -0
  302. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_unsigned_notes.py +0 -0
  303. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariants_metrics.py +0 -0
  304. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/language.py +0 -0
  305. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/link_targets.py +0 -0
  306. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/lock.py +0 -0
  307. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance.py +0 -0
  308. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_escalation.py +0 -0
  309. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_2.py +0 -0
  310. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_golden.py +0 -0
  311. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_graphfolds.py +0 -0
  312. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_healthlog.py +0 -0
  313. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_invariant_fields.py +0 -0
  314. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_outcomes.py +0 -0
  315. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_para.py +0 -0
  316. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_recommendations.py +0 -0
  317. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_sweep.py +0 -0
  318. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_watchdog.py +0 -0
  319. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_adapter.py +0 -0
  320. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_capture_verbs.py +0 -0
  321. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_housekeeping_verbs.py +0 -0
  322. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_mediation.py +0 -0
  323. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_supersede_verbs.py +0 -0
  324. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/__init__.py +0 -0
  325. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/capture.py +0 -0
  326. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/core_read.py +0 -0
  327. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/housekeeping.py +0 -0
  328. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/retrieval.py +0 -0
  329. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/supersession.py +0 -0
  330. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_verbs.py +0 -0
  331. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/model_slot.py +0 -0
  332. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/multihop.py +0 -0
  333. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/originals_resolve.py +0 -0
  334. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay_core.py +0 -0
  335. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay_keywords.py +0 -0
  336. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay_rules.py +0 -0
  337. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/pathkey.py +0 -0
  338. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/progress.py +0 -0
  339. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/projection.py +0 -0
  340. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provenance.py +0 -0
  341. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provision_mcp.py +0 -0
  342. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provision_stage.py +0 -0
  343. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provision_wire.py +0 -0
  344. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/querylog_replay.py +0 -0
  345. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_answers.py +0 -0
  346. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_branches.py +0 -0
  347. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_converged.py +0 -0
  348. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_exceptions.py +0 -0
  349. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_invariants.py +0 -0
  350. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_questions.py +0 -0
  351. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_routing.py +0 -0
  352. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_schema.py +0 -0
  353. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/retrieval_ops.py +0 -0
  354. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/retro.py +0 -0
  355. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/session_hook.py +0 -0
  356. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/session_hook_spec.py +0 -0
  357. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/status_ops.py +0 -0
  358. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/supersede_declared.py +0 -0
  359. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/sweepdirs.py +0 -0
  360. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_channels_2.py +0 -0
  361. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_cos_guard.py +0 -0
  362. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_model.py +0 -0
  363. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_plugins.py +0 -0
  364. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/versionlink.py +0 -0
  365. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/versionlink_stages.py +0 -0
  366. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/dependency_links.txt +0 -0
  367. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/entry_points.txt +0 -0
  368. {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainiac-cli
3
- Version: 0.20.48
3
+ Version: 0.20.49
4
4
  Summary: Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index).
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/Autopsias/brainiac
@@ -19,7 +19,7 @@ Requires-Dist: cryptography>=49
19
19
  Requires-Dist: PyYAML>=6
20
20
  Requires-Dist: regex>=2023.0.0
21
21
  Requires-Dist: keyring>=24; sys_platform == "win32"
22
- Requires-Dist: pypdf>=6.21.2
22
+ Requires-Dist: pypdf>=6.21.4
23
23
  Requires-Dist: python-docx>=1.1
24
24
  Requires-Dist: python-pptx>=0.6.23
25
25
  Requires-Dist: openpyxl>=3.1
@@ -44,7 +44,7 @@ Requires-Dist: PyYAML>=6; extra == "corporate"
44
44
  Provides-Extra: yaml
45
45
  Requires-Dist: PyYAML>=6; extra == "yaml"
46
46
  Provides-Extra: ingest
47
- Requires-Dist: pypdf>=6.21.2; extra == "ingest"
47
+ Requires-Dist: pypdf>=6.21.4; extra == "ingest"
48
48
  Requires-Dist: python-docx>=1.1; extra == "ingest"
49
49
  Requires-Dist: python-pptx>=0.6.23; extra == "ingest"
50
50
  Requires-Dist: openpyxl>=3.1; extra == "ingest"
@@ -63,6 +63,7 @@ Provides-Extra: quant-tools
63
63
  Requires-Dist: onnx>=1.14; extra == "quant-tools"
64
64
  Provides-Extra: mcp
65
65
  Requires-Dist: mcp<2,>=1.30.0; extra == "mcp"
66
+ Requires-Dist: PyJWT>=2.15.0; extra == "mcp"
66
67
  Provides-Extra: index
67
68
  Requires-Dist: regex>=2023.0.0; extra == "index"
68
69
  Dynamic: license-file
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
8
8
  # check. The IMPORT package stays `brain` and the console script stays
9
9
  # `brain` — only the distribution/project name changes.
10
10
  name = "brainiac-cli"
11
- version = "0.20.48"
11
+ version = "0.20.49"
12
12
  description = "Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index)."
13
13
  readme = "README.md"
14
14
  license = "Apache-2.0"
@@ -42,7 +42,7 @@ dependencies = [
42
42
  # because requirements.lock is CI/audit-only -- a plain
43
43
  # `pip install brainiac-cli` reads THIS constraint, and >=4.0 let the
44
44
  # vulnerable 6.14.2 survive an upgrade to 0.20.5 on a real host.
45
- "pypdf>=6.21.2",
45
+ "pypdf>=6.21.4",
46
46
  "python-docx>=1.1",
47
47
  "python-pptx>=0.6.23",
48
48
  "openpyxl>=3.1",
@@ -97,7 +97,7 @@ yaml = ["PyYAML>=6"]
97
97
  # message hints valid even though a default install already has these. Absent,
98
98
  # `brain ingest` degrades per-handler (reports which extensions are
99
99
  # unavailable) rather than crashing.
100
- ingest = ["pypdf>=6.21.2", "python-docx>=1.1", "python-pptx>=0.6.23", "openpyxl>=3.1", "Pillow>=13.0.0; python_version >= '3.11'", "Pillow>=12.3.0; python_version < '3.11'"]
100
+ ingest = ["pypdf>=6.21.4", "python-docx>=1.1", "python-pptx>=0.6.23", "openpyxl>=3.1", "Pillow>=13.0.0; python_version >= '3.11'", "Pillow>=12.3.0; python_version < '3.11'"]
101
101
  # pytest-xdist/-timeout back the documented parallel suite run (AGENTS.md §8,
102
102
  # "Running the test suite"): ~15 min sequential -> ~6 min at `-n 8`. Both are
103
103
  # pytest-dev org, actively maintained. Declared here so a fresh checkout can
@@ -128,7 +128,9 @@ quant-tools = ["onnx>=1.14"]
128
128
  # >=1.30.0: CVE-2026-59951 (Streamable HTTP stateful sessions never reclaimed,
129
129
  # unbounded memory) is fixed there. The FLOOR, not just the lock, for the same
130
130
  # reason as pypdf above: a plain `pip install` reads this, not requirements.lock.
131
- mcp = ["mcp>=1.30.0,<2"]
131
+ # PyJWT>=2.15.0: the floor mcp pulls in, stated here so a plain install
132
+ # cannot resolve an older PyJWT (public PR #36).
133
+ mcp = ["mcp>=1.30.0,<2", "PyJWT>=2.15.0"]
132
134
  # Timeout-bounded regex engine (mrab-regex) used by BrainIndex.grep's --regex
133
135
  # path (RET-04 hardening) as a ReDoS guard: regex.search(text, timeout=...)
134
136
  # genuinely bounds catastrophic backtracking, unlike stdlib `re`. Absent ->
@@ -183,7 +185,7 @@ known_first_party = ["brain", "workspace_registry", "framework_sync", "qwen3_emb
183
185
  # package_module_name_map: the 4 real import-name mismatches (distribution
184
186
  # name vs. the name actually imported).
185
187
  package_module_name_map = { PyYAML = "yaml", "python-docx" = "docx", "python-pptx" = "pptx", Pillow = "PIL" }
186
- per_rule_ignores = { DEP001 = ["llama_cpp", "lxml"], DEP002 = ["pytest", "pytest-xdist", "pytest-timeout", "ruff", "build", "onnx", "ranx"] }
188
+ per_rule_ignores = { DEP001 = ["llama_cpp", "lxml"], DEP002 = ["pytest", "pytest-xdist", "pytest-timeout", "ruff", "build", "onnx", "ranx", "PyJWT"] }
187
189
  # DEP001 llama_cpp/lxml: both are optional accelerants guarded by
188
190
  # try/except ImportError (src/brain/context.py, src/brain/ingest/handlers/html.py)
189
191
  # — the code degrades gracefully absent either, same pattern as the existing
@@ -195,12 +197,15 @@ per_rule_ignores = { DEP001 = ["llama_cpp", "lxml"], DEP002 = ["pytest", "pytest
195
197
  # src/brain package. ranx is a real [eval] runtime import, but its only
196
198
  # import site is eval/, which extend_exclude drops above for the DEP001
197
199
  # noise reason — excluding it flips ranx to a false DEP002.
200
+ # DEP002 PyJWT: a deliberate floor pin for mcp (see the mcp extra above,
201
+ # public PR #36); never imported by our own code.
198
202
 
199
203
  [tool.claude-quality]
200
204
  # Trees the size gates skip: frozen plan/archive/evidence dirs and the
201
205
  # generated _assets mirror of tools/ + scripts/ (re-synced by
202
206
  # tools/package_clients.py). Not authored source.
203
- exclude = ["_plans", "_archive", "_evidence", "_assets"]
207
+ # eval/ holds frozen, one-off experiment scripts, not shipped source (owner choice 2026-11-03).
208
+ exclude = ["_plans", "_archive", "_evidence", "_assets", "eval"]
204
209
 
205
210
  [tool.ruff]
206
211
  # CORRECTNESS ONLY — this is not a style gate. The repo already has three
@@ -257,6 +262,9 @@ select = ["F", "E9", "E722"]
257
262
  # exists to express that.
258
263
  ignore = ["F403", "F405"]
259
264
 
265
+ [tool.ruff.lint.mccabe]
266
+ max-complexity = 12 # matches the complexity ratchet limit (tools/check_complexity.py, limit=12)
267
+
260
268
  [tool.ruff.lint.per-file-ignores]
261
269
  # F811 cannot see pytest. A module-level `@pytest.fixture def text()` and a test
262
270
  # that takes `text` as a parameter is the framework's core idiom, and this rule
@@ -264,6 +272,10 @@ ignore = ["F403", "F405"]
264
272
  # real F811s all live outside tests/.
265
273
  "tests/**" = ["F811"]
266
274
 
275
+ # eval/ is out of the size and complexity gates (see [tool.claude-quality]); the
276
+ # correctness lint still runs on it.
277
+ "eval/**" = ["C901"]
278
+
267
279
 
268
280
  [tool.pytest.ini_options]
269
281
  # Scope collection to tests/ so pytest never descends into a local model venv
@@ -302,9 +314,9 @@ exclude = ["**/_plans", "**/_archive", "**/_evidence", "**/_assets"]
302
314
 
303
315
  [tool.uv]
304
316
  # Wait 7 days before resolving a newly published version (a new release can be
305
- # malicious or broken). shortcut: pypdf 6.21.2, filelock 4.2.4 and certifi
317
+ # malicious or broken). shortcut: pypdf 6.21.4 (fixes 7 DoS advisories, 2026-10-31), filelock 4.2.4 and certifi
306
318
  # 2026.10.23 are security fixes under 7 days old on 2026-10-24, so they are
307
- # exempt; remove the exemptions after 2026-10-31.
319
+ # exempt; re-check the exemptions after 2026-11-08.
308
320
  exclude-newer = "7 days"
309
321
  exclude-newer-package = { pypdf = false, filelock = false, certifi = false }
310
322
  # fastembed (the optional `embed` extra, not installed by default) caps
@@ -449,7 +449,7 @@ gates.
449
449
  signal (`emitter_output`, 2026-08-20).** The paragraph above is right and
450
450
  is not weakened: `proposal`, `report`, `analysis` and `review` stay OUT of
451
451
  `OPERATIONAL_SOURCE_TYPES` because a human writes those about the
452
- business. The gap it leaves is narrower — when `_log_rotation.py` emits
452
+ business. The gap it leaves is narrower — when a log-rotation script (not tracked here) emits
453
453
  `type: proposal` once a day, type says "keep" and nothing else disagrees.
454
454
  The reference vault carried **22** daily `Log rotation proposal` files
455
455
  from one script, each counted as a source awaiting a note. So the second
@@ -58,7 +58,11 @@ existing-note link suggestions, or missing watchdogs in action_required rather
58
58
  than changing existing notes. This runner never edits or retires existing notes.
59
59
  Do not create a duplicate of a supplied existing note. If there is no honest
60
60
  new synthesis, return notes=[] and explain why in summary/action_required.
61
+ Maintenance status="ok" with a recent last_run is success; last_success and rc
62
+ are optional fields on many folds. Missing optional fields are not failures.
61
63
  Note bodies must be 120–8000 characters; titles 1–200 characters.
64
+ Use each distinct set of source_ids for at most one note. Combine related
65
+ claims from the same source set rather than repeating that set in several notes.
62
66
 
63
67
  UNTRUSTED DATA PACKET:
64
68
  {packet}
@@ -113,8 +117,10 @@ def make_packet(args, core):
113
117
  lane = link_lane_candidates(core.index.conn, args.vault, limit=100000)
114
118
  gate = classification.ClassificationFilter(args.max_tier)
115
119
  eligible = [x for x in lane["candidates"] if gate.allows(x.get("classification"))]
120
+ offset = getattr(args, "source_offset", 0) % max(len(eligible), 1)
121
+ ordered = eligible[offset:] + eligible[:offset]
116
122
  sources, remaining = [], args.packet_chars
117
- for row in eligible:
123
+ for row in ordered:
118
124
  if len(sources) >= args.source_budget or remaining < 120:
119
125
  break
120
126
  note = get_gated(args, row["id"])
@@ -145,7 +151,7 @@ def make_packet(args, core):
145
151
  except (OSError, ValueError):
146
152
  state = {}
147
153
  # Diagnostics expose cadence/status, not arbitrary strings from host stores.
148
- diagnostics = {key: {k: value.get(k) for k in ("last_run", "last_success", "date", "rc")}
154
+ diagnostics = {key: {k: value.get(k) for k in ("last_run", "last_success", "date", "rc", "status", "failed", "consecutive_failures")}
149
155
  for key, value in state.items() if isinstance(value, dict)}
150
156
  return {"sources": sources, "knowledge_context": knowledge,
151
157
  "diagnostics": diagnostics,
@@ -322,6 +328,8 @@ def parser():
322
328
  p.add_argument("--model", default=None)
323
329
  p.add_argument("--max-tier", choices=classification.TIERS, default="Internal")
324
330
  p.add_argument("--source-budget", type=int, default=40)
331
+ p.add_argument("--source-offset", type=int, default=0,
332
+ help="Rotate the canonical eligible lane for an operator-run backfill; no exclusions")
325
333
  p.add_argument("--note-budget", type=int, default=8)
326
334
  p.add_argument("--packet-chars", type=int, default=120000)
327
335
  p.add_argument("--timeout", type=int, default=1200)
@@ -337,7 +345,7 @@ def parser():
337
345
 
338
346
 
339
347
  def validate_args(args):
340
- if not (1 <= args.source_budget <= 40 and 1 <= args.note_budget <= 8
348
+ if not (0 <= args.source_offset <= 100000 and 1 <= args.source_budget <= 40 and 1 <= args.note_budget <= 8
341
349
  and 1000 <= args.packet_chars <= 120000 and 30 <= args.timeout <= 1800):
342
350
  raise SystemExit("Bounds refused")
343
351
  for binary in (args.brain_bin, args.codex_bin):
@@ -441,6 +449,11 @@ def main():
441
449
  # A dry-run/smoke test must never earn the production watchdog heartbeat.
442
450
  if not args.smoke_test and not args.dry_run:
443
451
  update_state(args, outcome)
452
+ # Numbers only: summary/action_required/error carry vault-derived text and
453
+ # stay in the owner-only run_dir, never in the launchd log this lands in.
454
+ print(json.dumps({"rc": outcome["rc"], "run_dir": outcome["run_dir"],
455
+ "created": len(outcome.get("created") or []),
456
+ **{k: outcome.get(k) for k in ("tokens", "duration_s", "est_cost_usd")}}))
444
457
  return outcome["rc"]
445
458
 
446
459
 
@@ -9,4 +9,4 @@ pip-installed host, importlib.metadata stays primary; brain/__init__.py reads
9
9
  this file only as the fallback before 0.0.0+unknown.
10
10
  """
11
11
 
12
- __version__ = "0.20.48"
12
+ __version__ = "0.20.49"
@@ -79,7 +79,7 @@ SYNTHESIS_STALE_DAYS = 8
79
79
  STUCK_DRAFT_HOURS = 48
80
80
 
81
81
  # The feed's `findings[].key` vocabulary is closed (see maintenance_notify.py:
82
- # "blocked", "synthesis-watchdog", "trend:<metric>", "invariant:<metric>",
82
+ # "blocked", "synthesis-watchdog", "trend:<metric>", "invariant:<metric>", # noqa: ERA001
83
83
  # "branch-escalate:<branch>", "ingest_quarantine*", ...). The dir is on the
84
84
  # VirtioFS mount the Cowork VM can write, so a key failing this shape must
85
85
  # never reach the rendered alert text — it is the one thing standing between
@@ -91,7 +91,7 @@ def _read_json(path: Path) -> Any:
91
91
  try:
92
92
  with open(path, encoding="utf-8") as fh:
93
93
  return json.load(fh)
94
- except Exception:
94
+ except Exception: # fail-open: missing or corrupt feed file reads as no data
95
95
  return None
96
96
 
97
97
 
@@ -0,0 +1,49 @@
1
+ """Durable-write temp-name helper and the one write-then-replace helper."""
2
+ from __future__ import annotations
3
+
4
+ import errno
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from .lock import _atomic_temp_path
11
+
12
+ atomic_temp_path = _atomic_temp_path
13
+
14
+
15
+ def _fsync_dir(d: Path) -> None:
16
+ """fsync a directory so a rename in it survives a crash; unsupported is fine."""
17
+ if os.name == "nt":
18
+ return # no directory descriptors on Windows
19
+ fd = os.open(d, os.O_RDONLY)
20
+ try:
21
+ os.fsync(fd)
22
+ except OSError as exc:
23
+ if exc.errno not in (errno.EINVAL, errno.ENOTSUP, errno.EBADF):
24
+ raise
25
+ finally:
26
+ os.close(fd)
27
+
28
+
29
+ def write_text_atomic(path: Path, text: str) -> None:
30
+ """Write ``text`` to a sibling temp file, fsync, then ``os.replace`` it in.
31
+
32
+ The temp file is removed on any error, so the target keeps its old content.
33
+ """
34
+ tmp = atomic_temp_path(path)
35
+ try:
36
+ with open(tmp, "w", encoding="utf-8") as fh:
37
+ fh.write(text)
38
+ fh.flush()
39
+ os.fsync(fh.fileno())
40
+ os.replace(tmp, path)
41
+ except BaseException:
42
+ tmp.unlink(missing_ok=True)
43
+ raise
44
+ _fsync_dir(path.parent) # after the replace: the target must not be unlinked on failure
45
+
46
+
47
+ def write_json_atomic(path: Path, obj: Any) -> None:
48
+ """``write_text_atomic`` of ``json.dumps(obj, indent=2)`` plus a newline."""
49
+ write_text_atomic(path, json.dumps(obj, indent=2) + "\n")
@@ -18,6 +18,7 @@ Resolution precedence (first hit wins, all yield identical PEM bytes):
18
18
  """
19
19
  from __future__ import annotations
20
20
 
21
+ import logging
21
22
  import base64 as base64
22
23
  import contextlib
23
24
  import hashlib
@@ -31,6 +32,8 @@ from datetime import datetime as datetime, timezone as timezone
31
32
  from pathlib import Path
32
33
  from typing import Iterator, Optional
33
34
 
35
+ logger = logging.getLogger(__name__)
36
+
34
37
  JSONL_FORMAT = "brain-audit-chain-jsonl-v1"
35
38
  NULL_PREV_HASH = "0" * 64
36
39
  _REQUIRED_KEYS = frozenset({"format", "ts", "verb", "path", "reason", "prev_hash", "sig"})
@@ -137,7 +140,7 @@ def _pem_from_credential_manager(service: str, account: str) -> Optional[bytes]:
137
140
 
138
141
  secret = keyring.get_password(service, account)
139
142
  return secret.encode("utf-8") if secret else None
140
- except Exception:
143
+ except Exception: # fail-open: keyring is optional; None means no key from this source
141
144
  return None
142
145
 
143
146
 
@@ -334,7 +337,8 @@ def _exclusive_lock(log_path: Path) -> Iterator[None]:
334
337
 
335
338
  msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
336
339
  locked_with = "msvcrt"
337
- except Exception:
340
+ except Exception: # fail-open: best-effort lock; locked_with=None records that none was taken
341
+ logger.warning("audit lock not taken (msvcrt locking failed); writing unlocked", exc_info=True)
338
342
  locked_with = None # best-effort fallback
339
343
  yield
340
344
  finally:
@@ -348,7 +352,7 @@ def _exclusive_lock(log_path: Path) -> Iterator[None]:
348
352
 
349
353
  os.lseek(fd, 0, os.SEEK_SET)
350
354
  msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
351
- except Exception:
355
+ except Exception: # fail-open: unlock failure is harmless, the fd is closed next
352
356
  pass
353
357
  os.close(fd)
354
358
 
@@ -1,12 +1,11 @@
1
1
  """Host-private audit drift-disposition triage (INT-02)."""
2
2
  from __future__ import annotations
3
+ from .atomic_temp import write_text_atomic
3
4
 
4
5
  import json
5
- import os
6
6
  from datetime import date
7
7
  from pathlib import Path
8
8
  from typing import TYPE_CHECKING
9
- from .atomic_temp import atomic_temp_path
10
9
 
11
10
  if TYPE_CHECKING: # annotation-only; a runtime import here would be a cycle
12
11
  from .audit_chain import AuditChain
@@ -33,9 +32,7 @@ def _write_dispositions(path: Path, payload: dict) -> None:
33
32
  turn every explained finding back into an unexplained one. `os.replace` is
34
33
  atomic within a filesystem, so a reader sees the old file or the new one.
35
34
  """
36
- tmp = atomic_temp_path(path)
37
- tmp.write_text(json.dumps(payload, indent=2), encoding="utf-8")
38
- os.replace(tmp, path)
35
+ write_text_atomic(path, json.dumps(payload, indent=2))
39
36
 
40
37
 
41
38
  def drift_dispositions_path(vault: Path) -> Path:
@@ -45,16 +45,18 @@ def _fold(text: str) -> str:
45
45
  return re.sub(r"[^a-z0-9]+", "-", folded).strip("-")
46
46
 
47
47
 
48
- def _typed_notes(vault: Path, types: tuple[str, ...]) -> list[tuple[str, str]]:
49
- """[(note_id, folded-title-slug)] for every brain/ note of one of ``types``."""
48
+ def _typed_notes(vault: Path, types: tuple[str, ...], notes=None) -> list[tuple[str, str]]:
49
+ """[(note_id, folded-title-slug)] for every brain/ note of one of ``types``.
50
+
51
+ ``notes`` is an already-scanned vault (one scan shared by both finders)."""
50
52
  out: list[tuple[str, str]] = []
51
- for note in scan_vault(vault):
53
+ for note in (scan_vault(vault) if notes is None else notes):
52
54
  if note.zone == "brain" and note.type in types:
53
55
  out.append((note.id, _fold(note.title)))
54
56
  return out
55
57
 
56
58
 
57
- def find_person_links(text: str, vault: Path) -> list[str]:
59
+ def find_person_links(text: str, vault: Path, notes=None) -> list[str]:
58
60
  """Full-name matches of existing ``type: person`` notes in ``text``.
59
61
 
60
62
  A person note's title must fold to at least two hyphen-separated parts
@@ -63,7 +65,7 @@ def find_person_links(text: str, vault: Path) -> list[str]:
63
65
  ``text`` never links on its own."""
64
66
  folded_text = _fold(text)
65
67
  hits: list[str] = []
66
- for note_id, name_slug in _typed_notes(vault, ("person",)):
68
+ for note_id, name_slug in _typed_notes(vault, ("person",), notes):
67
69
  parts = [p for p in name_slug.split("-") if p]
68
70
  if len(parts) < 2:
69
71
  continue
@@ -72,7 +74,7 @@ def find_person_links(text: str, vault: Path) -> list[str]:
72
74
  return hits
73
75
 
74
76
 
75
- def find_project_link(text: str, vault: Path) -> list[str]:
77
+ def find_project_link(text: str, vault: Path, notes=None) -> list[str]:
76
78
  """Origin-context match against ``type: project``/``type: moc`` notes.
77
79
 
78
80
  Returns a match only when exactly one candidate's folded title is a
@@ -80,7 +82,7 @@ def find_project_link(text: str, vault: Path) -> list[str]:
80
82
  nothing."""
81
83
  folded_text = _fold(text)
82
84
  candidates = [
83
- note_id for note_id, title_slug in _typed_notes(vault, ("project", "moc"))
85
+ note_id for note_id, title_slug in _typed_notes(vault, ("project", "moc"), notes)
84
86
  if title_slug and title_slug in folded_text
85
87
  ]
86
88
  return candidates if len(candidates) == 1 else []
@@ -109,8 +111,9 @@ def apply_autolinks(
109
111
  return body, []
110
112
  try:
111
113
  search_text = "\n".join(t for t in (title, body) if t)
112
- candidates = list(find_person_links(search_text, vault))
113
- candidates.extend(find_project_link("\n".join(t for t in (title, origin) if t), vault))
114
+ notes = list(scan_vault(vault)) # one scan per record; no cross-record cache
115
+ candidates = list(find_person_links(search_text, vault, notes))
116
+ candidates.extend(find_project_link("\n".join(t for t in (title, origin) if t), vault, notes))
114
117
  existing = set(parse_wikilinks(body))
115
118
  new_ids = [nid for nid in dict.fromkeys(candidates) if nid not in existing]
116
119
  if not new_ids:
@@ -21,17 +21,28 @@ from __future__ import annotations
21
21
  import hashlib
22
22
  import io
23
23
  import json
24
+ import os
24
25
  import tarfile
25
26
  import time
26
27
  from dataclasses import asdict, dataclass
27
28
  from pathlib import Path
28
29
  from typing import Any
29
30
 
31
+ from . import config
30
32
  from . import encryption as enc
31
33
 
32
34
  EXCLUDE_DIRS = {".brain", ".git", "__pycache__", ".pytest_cache"}
33
35
 
34
36
 
37
+
38
+ def _write_owner_only(path: Path, data: bytes) -> None:
39
+ """Create the archive 0600 from the first byte: a plaintext backup is the
40
+ whole vault, and the default umask left it readable by every local user."""
41
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
42
+ config.secure_file_permissions(path, 0o600) # O_CREAT ignores mode on an existing file
43
+ with os.fdopen(fd, "wb") as fh:
44
+ fh.write(data)
45
+
35
46
  @dataclass
36
47
  class BackupManifest:
37
48
  schema_version: str
@@ -88,11 +99,11 @@ def create_backup(vault: Path, dest_dir: Path, *, name: str | None = None,
88
99
  if encrypt:
89
100
  token = enc.encrypt_bytes(plaintext, force=True) # force: off-device => encrypt
90
101
  archive = dest_dir / f"{stamp}.tar.gz.enc"
91
- archive.write_bytes(token)
102
+ _write_owner_only(archive, token)
92
103
  ct_bytes = len(token)
93
104
  else:
94
105
  archive = dest_dir / f"{stamp}.tar.gz"
95
- archive.write_bytes(plaintext)
106
+ _write_owner_only(archive, plaintext)
96
107
  ct_bytes = len(plaintext)
97
108
 
98
109
  manifest = BackupManifest(
@@ -137,7 +137,26 @@ def format_brief(brief: dict[str, Any]) -> str:
137
137
  elif brief.get("pending_before_drain", 0) == 0:
138
138
  lines.append(" ✓ no pending captures")
139
139
 
140
- live = brief.get("cos_liveness") or {}
140
+ lines.extend(_cos_liveness_lines(brief.get("cos_liveness") or {}))
141
+
142
+ snap = brief.get("snapshot_age")
143
+ if snap:
144
+ lines.append(f" snapshot age: {snap}")
145
+
146
+ if brief.get("recent"):
147
+ lines.append(" recent:")
148
+ for n in brief["recent"]:
149
+ lines.append(
150
+ f" {str(n.get('updated', ''))[:10]} {n.get('id', '')} "
151
+ f"({n.get('classification') or 'UNLABELLED'})"
152
+ )
153
+
154
+ return "\n".join(lines)
155
+
156
+
157
+ def _cos_liveness_lines(live: dict[str, Any]) -> list[str]:
158
+ """COS liveness banner lines for the text brief."""
159
+ lines: list[str] = []
141
160
  if live.get("alert"):
142
161
  lines.append(f" ⚠ {live['alert_text']}")
143
162
  elif live.get("pending_behind_backpressure"):
@@ -158,20 +177,7 @@ def format_brief(brief: dict[str, Any]) -> str:
158
177
  # VALID run. Loud here for the same reason the line above is.
159
178
  if live.get("quarantine_text"):
160
179
  lines.append(f" ⚠ {live['quarantine_text']}")
161
-
162
- snap = brief.get("snapshot_age")
163
- if snap:
164
- lines.append(f" snapshot age: {snap}")
165
-
166
- if brief.get("recent"):
167
- lines.append(" recent:")
168
- for n in brief["recent"]:
169
- lines.append(
170
- f" {str(n.get('updated', ''))[:10]} {n.get('id', '')} "
171
- f"({n.get('classification') or 'UNLABELLED'})"
172
- )
173
-
174
- return "\n".join(lines)
180
+ return lines
175
181
 
176
182
 
177
183
  def _maintain_alert_lines(maintain_alert: dict[str, Any] | None) -> list[str]:
@@ -200,25 +200,8 @@ def _pack(heading: str, text: str, ordinal_start: int, lang_of) -> list[Chunk]:
200
200
  return out
201
201
 
202
202
 
203
- def chunk_text(body: str, *, lang_of=detect_language) -> list[Chunk]:
204
- """Chunk a note body at section/block level with a soft size budget.
205
-
206
- Strategy: split into heading-scoped blocks, then greedily merge adjacent
207
- blocks under the same heading up to TARGET_CHARS, splitting any single block
208
- over MAX_CHARS. Each chunk's language is detected on its own text.
209
-
210
- Dual-granularity (RET-03): a short note (whole body ≤ ``_whole_note_max()``)
211
- is returned as ONE whole-note chunk — its sections are NOT split — so its
212
- cross-lingual signal stays concentrated in a single vector. See
213
- ``WHOLE_NOTE_MAX_CHARS``.
214
- """
215
- whole_max = _whole_note_max()
216
- stripped = body.strip()
217
- if whole_max > 0 and stripped and len(stripped) <= whole_max:
218
- return [Chunk(0, "", stripped, lang_of(stripped))]
219
- blocks = _split_blocks(body)
220
- if not blocks:
221
- return []
203
+ def _merge_blocks(blocks: list[tuple[str, str]], lang_of) -> list[Chunk]:
204
+ """Greedily merge adjacent same-heading blocks up to TARGET_CHARS and pack."""
222
205
  chunks: list[Chunk] = []
223
206
  ordinal = 0
224
207
  pending_heading = blocks[0][0]
@@ -247,3 +230,25 @@ def chunk_text(body: str, *, lang_of=detect_language) -> list[Chunk]:
247
230
  if pending_text:
248
231
  emit(pending_heading, pending_text)
249
232
  return chunks
233
+
234
+
235
+ def chunk_text(body: str, *, lang_of=detect_language) -> list[Chunk]:
236
+ """Chunk a note body at section/block level with a soft size budget.
237
+
238
+ Strategy: split into heading-scoped blocks, then greedily merge adjacent
239
+ blocks under the same heading up to TARGET_CHARS, splitting any single block
240
+ over MAX_CHARS. Each chunk's language is detected on its own text.
241
+
242
+ Dual-granularity (RET-03): a short note (whole body ≤ ``_whole_note_max()``)
243
+ is returned as ONE whole-note chunk — its sections are NOT split — so its
244
+ cross-lingual signal stays concentrated in a single vector. See
245
+ ``WHOLE_NOTE_MAX_CHARS``.
246
+ """
247
+ whole_max = _whole_note_max()
248
+ stripped = body.strip()
249
+ if whole_max > 0 and stripped and len(stripped) <= whole_max:
250
+ return [Chunk(0, "", stripped, lang_of(stripped))]
251
+ blocks = _split_blocks(body)
252
+ if not blocks:
253
+ return []
254
+ return _merge_blocks(blocks, lang_of)
@@ -389,7 +389,10 @@ def _prepare_invocation(args: Any, config: Any) -> str:
389
389
  # the session's own shell, so it can never raise a VM ceiling.
390
390
  from . import vm_ceiling
391
391
 
392
- ceiling, _provenance = vm_ceiling.resolved_ceiling()
392
+ # The ceiling of the vault this command OPENS, not $BRAIN_VAULT's (SEC-04).
393
+ explicit = getattr(args, "vault", None)
394
+ ceiling, _provenance = vm_ceiling.resolved_ceiling(
395
+ config.vault_root(explicit, allow_missing=True) if explicit else None)
393
396
  args.max_tier = cls.clamp_to(str(args.max_tier), ceiling)
394
397
  config.apply_role_embedder_policy(role)
395
398
  return role